emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[ELPA-diffs] ELPA branch, master, updated. 561e896deb28ae4b2d5cff09e8985


From: Dmitry Gutov
Subject: [ELPA-diffs] ELPA branch, master, updated. 561e896deb28ae4b2d5cff09e89853181b48a0e1
Date: Wed, 06 Nov 2013 17:54:49 +0000

This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "ELPA".

The branch, master has been updated
       via  561e896deb28ae4b2d5cff09e89853181b48a0e1 (commit)
       via  a84065d60f3e587b0a6b5e2e5379cc782950137f (commit)
       via  65fb725443ac6aa875094e97095fb581d3ec1525 (commit)
       via  c272dc6f9bd0441fb286b6d2cc6b1efb0847f2ac (commit)
       via  ef65afd7a35e68356f9e4992d81c370f965b4a07 (commit)
       via  f884d146ec6fd898e39643de59c2e3bc030ab161 (commit)
       via  01ef1981cbc7b95c5180c00bbbfa4d7ced213248 (commit)
       via  b3ff6eb6de01b68a9ef4b8a680b18f206c8fa4c8 (commit)
       via  b8dbe81707ebc95044f0e03bd11b65cd0faac10d (commit)
       via  d3e0424a107973a070bb04215caacf709f15625d (commit)
       via  3c1533610f6a7d5785161c74815360999b4892c4 (commit)
       via  88509ff9db6416ba257c0ab1d02494bc42b1fbaf (commit)
       via  962d8d58c97b7f6712bcbb9ed9d5935fa1439781 (commit)
       via  de0fe6d946df2387e06a799916b23d9abbdb8ffe (commit)
       via  c6aada7da653d7f1628bd81f29bd4bd2967daf77 (commit)
       via  7add7efbd54fe39b78752c2e2e74251edc3583ef (commit)
       via  63f774d3765b6ad3af0e732ec7c31851d85895e0 (commit)
       via  462b6a37705ec3100ea443eabc1f749662684f87 (commit)
       via  a93f25a3aa518d5c3c68d8ce42781ff1a4a6f3f3 (commit)
       via  59fea24dd6961e835726283f26e4d162fb452143 (commit)
       via  9954c60292ea5d7a5a7725d208ed2a94a0051e14 (commit)
       via  0dad204bd0eda8ec2b16496ba1fd5daf60c25318 (commit)
       via  72d28d82bc1d742a33cf5afa8180707b375eccbf (commit)
       via  1c7f0f3731355fd4002dcb97d1941fe823a5c6ad (commit)
      from  28af4b01108a9d572e680087488cf1b543e078df (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 561e896deb28ae4b2d5cff09e89853181b48a0e1
Merge: 28af4b0 a84065d
Author: Dmitry Gutov <address@hidden>
Date:   Wed Nov 6 19:53:12 2013 +0200

    Merge remote-tracking branch 'js2-mode/master'

diff --cc packages/js2-mode/.dir-locals.el
index 0000000,db35973..db35973
mode 000000,100644..100644
--- a/packages/js2-mode/.dir-locals.el
+++ b/packages/js2-mode/.dir-locals.el

commit a84065d60f3e587b0a6b5e2e5379cc782950137f
Author: Dmitry Gutov <address@hidden>
Date:   Wed Nov 6 03:33:50 2013 +0200

    Bump the version

diff --git a/js2-mode.el b/js2-mode.el
index 9eef6de..15fd78d 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -7,7 +7,7 @@
 ;;         Dmitry Gutov <address@hidden>
 ;; URL:  https://github.com/mooz/js2-mode/
 ;;       http://code.google.com/p/js2-mode/
-;; Version: 20130619
+;; Version: 20131106
 ;; Keywords: languages, javascript
 ;; Package-Requires: ((emacs "24.1"))
 

commit 65fb725443ac6aa875094e97095fb581d3ec1525
Author: Dmitry Gutov <address@hidden>
Date:   Wed Nov 6 03:27:02 2013 +0200

    Support Harmony spread operator, in arrays and calls
    
    Closes #121

diff --git a/js2-mode.el b/js2-mode.el
index 59c0cc8..9eef6de 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -3200,6 +3200,7 @@ The type field inherited from `js2-node' holds the 
operator."
                (cons js2-BITNOT "~")
                (cons js2-POS "+")       ; unary plus
                (cons js2-NEG "-")       ; unary minus
+               (cons js2-TRIPLEDOT "...")
                (cons js2-SHEQ "===")    ; shallow equality
                (cons js2-SHNE "!==")    ; shallow inequality
                (cons js2-ASSIGN "=")
@@ -3252,7 +3253,7 @@ The type field holds the actual assignment operator.")
                                                     len operand)))
   "AST node type for unary operator nodes.
 The type field can be NOT, BITNOT, POS, NEG, INC, DEC,
-TYPEOF, or DELPROP.  For INC or DEC, a 'postfix node
+TYPEOF, DELPROP or TRIPLEDOT.  For INC or DEC, a 'postfix node
 property is added if the operator follows the operand."
   operand)  ; a `js2-node' expression
 
@@ -8833,9 +8834,14 @@ Returns the list in reverse order.  Consumes the 
right-paren token."
   (let (result)
     (unless (js2-match-token js2-RP)
       (loop do
-            (if (= (js2-peek-token) js2-YIELD)
-                (js2-report-error "msg.yield.parenthesized"))
-            (push (js2-parse-assign-expr) result)
+            (let ((tt (js2-get-token)))
+              (if (= tt js2-YIELD)
+                  (js2-report-error "msg.yield.parenthesized"))
+              (if (and (= tt js2-TRIPLEDOT)
+                       (>= js2-language-version 200))
+                  (push (js2-make-unary tt 'js2-parse-assign-expr) result)
+                (js2-unget-token)
+                (push (js2-parse-assign-expr) result)))
             while
             (js2-match-token js2-COMMA))
       (js2-must-match js2-RP "msg.no.paren.arg")
@@ -9278,13 +9284,17 @@ array-literals, array comprehensions and regular 
expressions."
         (js2-unget-token)
         (setf continue nil
               pn (js2-parse-array-comprehension (car elems) pos)))
-
        ;; another element
        (t
         (unless after-lb-or-comma
           (js2-report-error "msg.no.bracket.arg"))
-        (js2-unget-token)
-        (push (js2-parse-assign-expr) elems)
+        (if (and (= tt js2-TRIPLEDOT)
+                 (>= js2-language-version 200))
+            ;; spread operator
+            (push (js2-make-unary tt 'js2-parse-assign-expr)
+                  elems)
+          (js2-unget-token)
+          (push (js2-parse-assign-expr) elems))
         (setq after-lb-or-comma nil
               after-comma nil))))
     (unless js2-is-in-destructuring
diff --git a/tests/parser.el b/tests/parser.el
index a9b717d..fd8c4c4 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -161,6 +161,14 @@ the test."
 (js2-deftest-parse function-with-rest-after-default-parameter
   "function foo(a = 1, ...rest) {\n}")
 
+;;; Spread operator
+
+(js2-deftest-parse spread-in-array-literal
+  "[1, ...[2, 3], 4, ...[5, 6]];")
+
+(js2-deftest-parse spread-in-function-call
+  "f(3, ...[t(2), t(3)], 42, ...[t(4)]);")
+
 ;;; Arrow functions
 
 (js2-deftest-parse arrow-function-with-empty-args-and-no-curlies

commit c272dc6f9bd0441fb286b6d2cc6b1efb0847f2ac
Author: Dmitry Gutov <address@hidden>
Date:   Sun Nov 3 07:09:51 2013 +0400

    Support Harmony generators (closes #119)

diff --git a/js2-mode.el b/js2-mode.el
index 9ec8dc6..59c0cc8 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -926,7 +926,6 @@ buffer text for your imports, using regular expressions.")
 (defconst js2-end-drops-off     #x1)
 (defconst js2-end-returns       #x2)
 (defconst js2-end-returns-value #x4)
-(defconst js2-end-yields        #x8)
 
 ;; Rhino awkwardly passes a statementLabel parameter to the
 ;; statementHelper() function, the main statement parser, which
@@ -1675,10 +1674,10 @@ the correct number of ARGS must be provided."
          "return statement is inconsistent with previous usage")
 
 (js2-msg "msg.generator.returns"
-         "TypeError: generator function '%s' returns a value")
+         "TypeError: legacy generator function '%s' returns a value")
 
 (js2-msg "msg.anon.generator.returns"
-         "TypeError: anonymous generator function returns a value")
+         "TypeError: anonymous legacy generator function returns a value")
 
 (js2-msg "msg.syntax"
          "syntax error")
@@ -2964,6 +2963,7 @@ a `js2-label-node' or the innermost enclosing loop.")
                                                        (name "")
                                                        params rest-p
                                                        body
+                                                       generator-type
                                                        lp rp)))
   "AST node for a function declaration.
 The `params' field is a Lisp list of nodes.  Each node is either a simple
@@ -2979,7 +2979,7 @@ The `params' field is a Lisp list of nodes.  Each node is 
either a simple
   rp               ; position of arg-list close-paren, or nil if omitted
   ignore-dynamic   ; ignore value of the dynamic-scope flag (interpreter only)
   needs-activation ; t if we need an activation object for this frame
-  is-generator     ; t if this function contains a yield
+  generator-type   ; STAR, LEGACY or nil
   member-expr)     ; nonstandard Ecma extension from Rhino
 
 (put 'cl-struct-js2-function-node 'js2-visitor 'js2-visit-function-node)
@@ -3002,7 +3002,9 @@ The `params' field is a Lisp list of nodes.  Each node is 
either a simple
          (body (js2-function-node-body n))
          (expr (not (eq (js2-function-node-form n) 'FUNCTION_STATEMENT))))
     (unless (or getter arrow)
-      (insert pad "function"))
+      (insert pad "function")
+      (when (eq (js2-function-node-generator-type n) 'STAR)
+        (insert "*")))
     (when name
       (insert " ")
       (js2-print-ast name 0))
@@ -7093,8 +7095,10 @@ Returns t on match, nil if no match."
         (js2-set-requires-activation))))
 
 (defun js2-set-is-generator ()
-  (if (js2-function-node-p js2-current-script-or-fn)
-      (setf (js2-function-node-is-generator js2-current-script-or-fn) t)))
+  (let ((fn-node js2-current-script-or-fn))
+    (when (and (js2-function-node-p fn-node)
+               (not (js2-function-node-generator-type fn-node)))
+      (setf (js2-function-node-generator-type js2-current-script-or-fn) 
'LEGACY))))
 
 (defun js2-must-have-xml ()
   (unless js2-compiler-xml-available
@@ -7393,18 +7397,19 @@ Last token scanned is the close-curly for the function 
body."
         (js2-add-strict-warning "msg.anon.no.return.value" nil pos end)))))
 
 (defun js2-parse-function-stmt ()
-  (let ((pos (js2-current-token-beg)))
+  (let ((pos (js2-current-token-beg))
+        (star-p (js2-match-token js2-MUL)))
     (js2-must-match js2-NAME "msg.unnamed.function.stmt")
     (let ((name (js2-create-name-node t))
           pn member-expr)
       (cond
        ((js2-match-token js2-LP)
-        (js2-parse-function 'FUNCTION_STATEMENT pos name))
+        (js2-parse-function 'FUNCTION_STATEMENT pos star-p name))
        (js2-allow-member-expr-as-function-name
         (setq member-expr (js2-parse-member-expr-tail nil name))
         (js2-parse-highlight-member-expr-fn-name member-expr)
         (js2-must-match js2-LP "msg.no.paren.parms")
-        (setf pn (js2-parse-function 'FUNCTION_STATEMENT pos)
+        (setf pn (js2-parse-function 'FUNCTION_STATEMENT pos star-p)
               (js2-function-node-member-expr pn) member-expr)
         pn)
        (t
@@ -7413,13 +7418,14 @@ Last token scanned is the close-curly for the function 
body."
 
 (defun js2-parse-function-expr ()
   (let ((pos (js2-current-token-beg))
+        (star-p (js2-match-token js2-MUL))
         name)
     (when (js2-match-token js2-NAME)
       (setq name (js2-create-name-node t)))
     (js2-must-match js2-LP "msg.no.paren.parms")
-    (js2-parse-function 'FUNCTION_EXPRESSION pos name)))
+    (js2-parse-function 'FUNCTION_EXPRESSION pos star-p name)))
 
-(defun js2-parse-function (function-type pos &optional name)
+(defun js2-parse-function (function-type pos star-p &optional name)
   "Function parser.  FUNCTION-TYPE is a symbol, POS is the
 beginning of the first token (function keyword, unless it's an
 arrow function), NAME is js2-name-node."
@@ -7429,7 +7435,8 @@ arrow function), NAME is js2-name-node."
     (setf fn-node (make-js2-function-node :pos pos
                                           :name name
                                           :form function-type
-                                          :lp (if lp (- lp pos))))
+                                          :lp (if lp (- lp pos))
+                                          :generator-type (and star-p 'STAR)))
     (when name
       (js2-set-face (js2-node-pos name) (js2-node-end name)
                     'font-lock-function-name-face 'record)
@@ -8146,7 +8153,6 @@ but not BEFORE."
      (t
       (unless (js2-inside-function)
         (js2-report-error "msg.bad.yield"))
-      (js2-set-flag js2-end-flags js2-end-yields)
       (setq ret (make-js2-yield-node :pos pos
                                      :len (- end pos)
                                      :value e))
@@ -8158,8 +8164,9 @@ but not BEFORE."
       (js2-set-is-generator))))
     ;; see if we are mixing yields and value returns.
     (when (and inside-function
-               (js2-now-all-set before js2-end-flags
-                                (logior js2-end-yields js2-end-returns-value)))
+               (js2-flag-set-p js2-end-flags js2-end-returns-value)
+               (eq (js2-function-node-generator-type js2-current-script-or-fn)
+                   'LEGACY))
       (setq name (js2-function-name js2-current-script-or-fn))
       (if (zerop (length name))
           (js2-report-error "msg.anon.generator.returns" nil pos (- end pos))
@@ -8519,7 +8526,7 @@ If NODE is non-nil, it is the AST node associated with 
the symbol."
              (>= js2-language-version 200))
         (js2-ts-seek ts-state)
         (setq js2-recorded-identifiers recorded-identifiers)
-        (setq pn (js2-parse-function 'FUNCTION_ARROW (js2-current-token-beg))))
+        (setq pn (js2-parse-function 'FUNCTION_ARROW (js2-current-token-beg) 
nil)))
        (t
         (js2-unget-token)))
       pn)))
diff --git a/tests/parser.el b/tests/parser.el
index 1463196..a9b717d 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -204,6 +204,15 @@ the test."
   }
 }")
 
+;;; Generators
+
+(js2-deftest-parse legacy-generator "function foo() {\n  yield 1;\n}")
+
+(js2-deftest-parse legacy-generator-cannot-return
+  "function foo() {\n  yield 1;\n return 2;\n}" :syntax-error "return 2")
+
+(js2-deftest-parse harmony-generator "function* bar() {\n  yield 2;\n  return 
3;\n}")
+
 ;;; Scopes
 
 (js2-deftest ast-symbol-table-includes-fn-node "function foo() {}"

commit ef65afd7a35e68356f9e4992d81c370f965b4a07
Author: Dmitry Gutov <address@hidden>
Date:   Fri Oct 18 08:58:30 2013 +0400

    Fix parsing labeled statements (and #120)
    
    + change labeled-stmt printer not to indent statements additionally

diff --git a/js2-mode.el b/js2-mode.el
index 4f92081..9ec8dc6 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -2885,7 +2885,7 @@ Returns nil if no such label is in the list."
 (defun js2-print-labeled-stmt (n i)
   (dolist (label (js2-labeled-stmt-node-labels n))
     (js2-print-ast label i))
-  (js2-print-ast (js2-labeled-stmt-node-stmt n) (1+ i)))
+  (js2-print-ast (js2-labeled-stmt-node-stmt n) i))
 
 (defun js2-labeled-stmt-node-contains (node label)
   "Return t if NODE contains LABEL in its label set.
@@ -8269,6 +8269,7 @@ expression and return it wrapped in a 
`js2-expr-stmt-node'."
       (unwind-protect
             (unless stmt
               (let ((js2-labeled-stmt bundle))  ; bind dynamically
+                (js2-unget-token)
                 (setq stmt (js2-statement-helper))))
         ;; remove the labels for this statement from the global set
         (dolist (label (js2-labeled-stmt-node-labels bundle))
diff --git a/tests/parser.el b/tests/parser.el
index f925636..1463196 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -189,7 +189,7 @@ the test."
 ;;; Labels
 
 (js2-deftest-parse labeled-stmt-node
-  "foo:\nbar:\n  x = y + 1;")
+  "foo:\nbar:\nx = y + 1;")
 
 (js2-deftest no-label-node-inside-expr "x = y:"
   (let (js2-parse-interruptable-p)
@@ -197,6 +197,13 @@ the test."
   (let ((assignment (js2-expr-stmt-node-expr (car (js2-scope-kids 
js2-mode-ast)))))
     (should (js2-name-node-p (js2-assign-node-right assignment)))))
 
+(js2-deftest-parse label-and-loops "for (; ; ) {
+  loop:
+  for (; ; ) {
+    continue loop;
+  }
+}")
+
 ;;; Scopes
 
 (js2-deftest ast-symbol-table-includes-fn-node "function foo() {}"

commit f884d146ec6fd898e39643de59c2e3bc030ab161
Author: Dmitry Gutov <address@hidden>
Date:   Sun Oct 13 06:09:10 2013 +0300

    Fix #116

diff --git a/js2-mode.el b/js2-mode.el
index e6eace1..4f92081 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -9803,7 +9803,11 @@ In particular, return the buffer position of the first 
`for' kwd."
     (let* ((ctrl-stmt-indent (js2-ctrl-statement-indentation))
            (at-closing-bracket (looking-at "[]})]"))
            (same-indent-p (or at-closing-bracket
-                              (looking-at "\\<case\\>\\|\\<default\\>")))
+                              (looking-at "\\<case\\>[^:]")
+                              (and (looking-at "\\<default:")
+                                   (save-excursion
+                                     (js2-backward-sws)
+                                     (not (memq (char-before) '(?, ?{)))))))
            (continued-expr-p (js2-continued-expression-p))
            (declaration-indent (and js2-pretty-multiline-declarations
                                     (js2-multiline-decl-indentation)))
diff --git a/tests/indent.el b/tests/indent.el
index 953b8a6..f4e8945 100644
--- a/tests/indent.el
+++ b/tests/indent.el
@@ -73,3 +73,10 @@
   |    },
   |    bar = 8;"
   :bind ((js2-pretty-multiline-declarations 'all)))
+
+(js2-deftest-indent default-keyword-as-property
+  "var foo = {
+  |  case: 'zzzz',
+  |  default: 'donkey',
+  |  tee: 'ornery'
+  |};")

commit 01ef1981cbc7b95c5180c00bbbfa4d7ced213248
Author: Dmitry Gutov <address@hidden>
Date:   Sun Oct 13 05:53:34 2013 +0300

    Add small clarification

diff --git a/tests/parser.el b/tests/parser.el
index 0176f93..f925636 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -214,6 +214,7 @@ the test."
   (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 (string= (js2-name-node-name (js2-function-node-name scope)) 
"foo"))
     (should (= (js2-symbol-decl-type fn-entry) js2-FUNCTION))
     (should (js2-function-node-p (js2-symbol-ast-node fn-entry)))
     (should (= (js2-symbol-decl-type var-entry) js2-VAR))

commit b3ff6eb6de01b68a9ef4b8a680b18f206c8fa4c8
Author: Dmitry Gutov <address@hidden>
Date:   Sun Oct 13 05:46:53 2013 +0300

    Include function's AST node in parent scope's symbol table entry
    
    Reported by Sergey Mogzovoy

diff --git a/js2-mode.el b/js2-mode.el
index 03be86b..e6eace1 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -7426,16 +7426,16 @@ arrow function), NAME is js2-name-node."
   (let (fn-node lp)
     (if (= (js2-current-token-type) js2-LP) ; eventually matched LP?
         (setq lp (js2-current-token-beg)))
+    (setf fn-node (make-js2-function-node :pos pos
+                                          :name name
+                                          :form function-type
+                                          :lp (if lp (- lp pos))))
     (when name
       (js2-set-face (js2-node-pos name) (js2-node-end name)
                     'font-lock-function-name-face 'record)
       (when (plusp (js2-name-node-length name))
         ;; Function statements define a symbol in the enclosing scope
         (js2-define-symbol js2-FUNCTION (js2-name-node-name name) fn-node)))
-    (setf fn-node (make-js2-function-node :pos pos
-                                          :name name
-                                          :form function-type
-                                          :lp (if lp (- lp pos))))
     (if (or (js2-inside-function) (plusp js2-nesting-of-with))
         ;; 1. Nested functions are not affected by the dynamic scope flag
         ;;    as dynamic scope is already a parent of their scope.
diff --git a/tests/parser.el b/tests/parser.el
index b84265e..0176f93 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -197,6 +197,28 @@ the test."
   (let ((assignment (js2-expr-stmt-node-expr (car (js2-scope-kids 
js2-mode-ast)))))
     (should (js2-name-node-p (js2-assign-node-right assignment)))))
 
+;;; Scopes
+
+(js2-deftest ast-symbol-table-includes-fn-node "function foo() {}"
+  (js2-mode)
+  (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"))
+    (should (js2-function-node-p (js2-symbol-ast-node entry)))))
+
+(js2-deftest fn-symbol-table-includes-nested-fn "function foo() {
+  function bar() {}
+  var x;
+}"
+  (js2-mode)
+  (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 (= (js2-symbol-decl-type fn-entry) js2-FUNCTION))
+    (should (js2-function-node-p (js2-symbol-ast-node fn-entry)))
+    (should (= (js2-symbol-decl-type var-entry) js2-VAR))
+    (should (js2-name-node-p (js2-symbol-ast-node var-entry)))))
+
 ;;; Tokenizer
 
 (js2-deftest get-token "(1+1)"

commit b8dbe81707ebc95044f0e03bd11b65cd0faac10d
Author: Dmitry Gutov <address@hidden>
Date:   Sun Oct 13 04:21:51 2013 +0300

    (Hopefully) fix #117 and capitaomorte/autopair#21

diff --git a/js2-mode.el b/js2-mode.el
index 4bd3af0..03be86b 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -5434,6 +5434,7 @@ its relevant fields and puts it into `js2-ti-tokens'."
           (setq c (js2-get-char))
           (cond
            ((eq c js2-EOF_CHAR)
+            (js2-unget-char)
             (js2-ts-set-char-token-bounds token)
             (throw 'return js2-EOF))
            ((eq c ?\n)
@@ -7407,7 +7408,8 @@ Last token scanned is the close-curly for the function 
body."
               (js2-function-node-member-expr pn) member-expr)
         pn)
        (t
-        (js2-report-error "msg.no.paren.parms"))))))
+        (js2-report-error "msg.no.paren.parms")
+        (make-js2-error-node))))))
 
 (defun js2-parse-function-expr ()
   (let ((pos (js2-current-token-beg))
@@ -8189,7 +8191,7 @@ Current token type is `js2-SEMI' or `js2-ERROR'."
     (if (eq tt js2-SEMI)
         (make-js2-empty-expr-node :len 1)
       (setq pos (js2-current-token-beg)
-            len (- (js2-current-token-beg) pos))
+            len (- (js2-current-token-end) pos))
       (js2-report-error "msg.syntax" nil pos len)
       (make-js2-error-node :pos pos :len len))))
 
@@ -9189,7 +9191,7 @@ array-literals, array comprehensions and regular 
expressions."
       (setq px-pos (point-at-bol)
             len (- js2-ts-cursor px-pos))
       (js2-report-error "msg.unexpected.eof" nil px-pos len)
-      (make-js2-error-node :pos px-pos :len len))
+      (make-js2-error-node :pos (1- js2-ts-cursor)))
      (t
       (js2-report-error "msg.syntax")
       (make-js2-error-node)))))
diff --git a/tests/parser.el b/tests/parser.el
index 0a076e1..b84265e 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -85,7 +85,7 @@ the test."
 (js2-deftest-parse named-function-expression
   "a = function b() {};")
 
-;;; Callers of `js2-valid-prop-name-token'.
+;;; Callers of `js2-valid-prop-name-token'
 
 (js2-deftest-parse parse-property-access-when-not-keyword
   "A.foo = 3;")
@@ -106,7 +106,7 @@ the test."
   "a = {in: 1};"
   :bind ((js2-allow-keywords-as-property-names t)))
 
-;;; 'of' contextual keyword.
+;;; 'of' contextual keyword
 
 (js2-deftest-parse parse-array-comp-loop-with-of
   "[a for (a of [])];")
@@ -120,7 +120,7 @@ the test."
 (js2-deftest-parse of-can-be-function-name
   "function of() {\n}")
 
-;;; Destructuring binding.
+;;; Destructuring binding
 
 (js2-deftest-parse destruct-in-declaration
   "var {a, b} = {a: 1, b: 2};")
@@ -134,7 +134,7 @@ the test."
 (js2-deftest-parse destruct-in-catch-clause
   "try {\n} catch ({a, b}) {\n  a + b;\n}")
 
-;;; Function parameters.
+;;; Function parameters
 
 (js2-deftest-parse function-with-default-parameters
   "function foo(a = 1, b = a + 1) {\n}")
@@ -161,7 +161,7 @@ the test."
 (js2-deftest-parse function-with-rest-after-default-parameter
   "function foo(a = 1, ...rest) {\n}")
 
-;;; Arrow functions.
+;;; Arrow functions
 
 (js2-deftest-parse arrow-function-with-empty-args-and-no-curlies
   "() => false;" :reference "() => {false};")
@@ -175,7 +175,7 @@ the test."
 (js2-deftest-parse parenless-arrow-function-prohibits-destructuring
   "[a, b] => {a + b;};" :syntax-error "]" :errors-count 5)
 
-;;; Automatic semicolon insertion.
+;;; Automatic semicolon insertion
 
 (js2-deftest-parse no-auto-semi-insertion-after-if
   "if (true) {\n}")
@@ -243,3 +243,14 @@ the test."
     (should (eq js2-NUMBER (js2-next-token)))
     (should (eq 1 (js2-token-number
                    (js2-current-token))))))
+
+;;; Error handling
+
+(js2-deftest for-node-with-error-len "for "
+  (js2-mode)
+  (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))

commit d3e0424a107973a070bb04215caacf709f15625d
Author: Dmitry Gutov <address@hidden>
Date:   Tue Oct 8 15:47:25 2013 +0300

    pn was indeed unused

diff --git a/js2-mode.el b/js2-mode.el
index 46acb07..4bd3af0 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -8245,13 +8245,13 @@ up any following labels and the next non-label 
statement into a
 `js2-labeled-stmt-node' bundle and return that.  Otherwise we parse an
 expression and return it wrapped in a `js2-expr-stmt-node'."
   (let ((pos (js2-current-token-beg))
-        expr stmt pn bundle
+        expr stmt bundle
         (continue t))
     ;; set check for label and call down to `js2-parse-primary-expr'
     (setq expr (js2-maybe-parse-label))
     (if (null expr)
         ;; Parse the non-label expression and wrap with expression stmt.
-        (setq pn (js2-wrap-with-expr-stmt pos (js2-parse-expr) t)) ;FIXME: 
`pn' is unused!
+        (js2-wrap-with-expr-stmt pos (js2-parse-expr) t)
       ;; else parsed a label
       (setq bundle (make-js2-labeled-stmt-node :pos pos))
       (js2-record-label expr bundle)

commit 3c1533610f6a7d5785161c74815360999b4892c4
Merge: 88509ff 63f774d
Author: Dmitry Gutov <address@hidden>
Date:   Tue Oct 8 15:46:16 2013 +0300

    Merge branch 'arrow-functions'
    
    Conflicts:
        js2-mode.el

diff --cc js2-mode.el
index ed92e63,0174d32..46acb07
--- a/js2-mode.el
+++ b/js2-mode.el
@@@ -2287,9 -2321,8 +2328,9 @@@ NAME can be a Lisp symbol or string.  S
              (:include js2-scope)
              (:constructor nil)
              (:constructor make-js2-script-node (&key (type js2-SCRIPT)
-                                                      (pos js2-token-beg)
+                                                      (pos 
(js2-current-token-beg))
                                                       len
 +                                                     ;; FIXME: What are those?
                                                       var-decls
                                                       fun-decls)))
    functions   ; Lisp list of nested functions
@@@ -5136,9 -5179,10 +5183,10 @@@ Returns nil and consumes nothing if TES
  
  (defun js2-skip-line ()
    "Skip to end of line."
 -  (let (c)
 -    (while (not (memq (setq c (js2-get-char)) js2-eol-chars)))
 -    (js2-unget-char)
 -    (setf (js2-token-end (js2-current-token)) js2-ts-cursor)))
 +  (while (not (memq (js2-get-char) js2-eol-chars)))
 +  (js2-unget-char)
++  (setf (js2-token-end (js2-current-token)) js2-ts-cursor)
 +  (setq js2-token-end js2-ts-cursor))
  
  (defun js2-init-scanner (&optional buf line)
    "Create token stream for BUF starting on LINE.
@@@ -5317,10 -5356,72 +5360,72 @@@ corresponding number.  Otherwise retur
      (logior c (lsh accumulator 4))))
  
  (defun js2-get-token ()
-   "Return next JavaScript token, an int such as js2-RETURN."
+   "If `js2-ti-lookahead' is zero, call scanner to get new token.
+ Otherwise, move `js2-ti-tokens-cursor' and return the type of
+ next saved token.
+ 
+ This function will not return a newline (js2-EOL) - instead, it
+ gobbles newlines until it finds a non-newline token.  Call
+ `js2-peek-token-or-eol' when you care about newlines.
+ 
+ This function will also not return a js2-COMMENT.  Instead, it
+ records comments found in `js2-scanned-comments'.  If the token
+ returned by this function immediately follows a jsdoc comment,
+ the token is flagged as such."
+   (if (zerop js2-ti-lookahead)
+       (js2-get-token-internal)
+     (decf js2-ti-lookahead)
+     (setq js2-ti-tokens-cursor (mod (1+ js2-ti-tokens-cursor) js2-ti-ntokens))
+     (let ((tt (js2-current-token-type)))
+       (assert (not (= tt js2-EOL)))
+       tt)))
+ 
+ (defun js2-unget-token ()
+   (assert (< js2-ti-lookahead js2-ti-max-lookahead))
+   (incf js2-ti-lookahead)
+   (setq js2-ti-tokens-cursor (mod (1- js2-ti-tokens-cursor) js2-ti-ntokens)))
+ 
+ (defun js2-get-token-internal ()
+   (let* ((token (js2-get-token-internal-1)) ; call scanner
+          (tt (js2-token-type token))
+          saw-eol
+          face)
+     ;; process comments
+     (while (or (= tt js2-EOL) (= tt js2-COMMENT))
+       (if (= tt js2-EOL)
+           (setq saw-eol t)
+         (setq saw-eol nil)
+         (when js2-record-comments
+           (js2-record-comment token)))
+       (setq js2-ti-tokens-cursor (mod (1- js2-ti-tokens-cursor) 
js2-ti-ntokens))
+       (setq token (js2-get-token-internal-1) ; call scanner again
+             tt (js2-token-type token)))
+ 
+     (when saw-eol
+       (setf (js2-token-follows-eol-p token) t))
+ 
+     ;; perform lexical fontification as soon as token is scanned
+     (when js2-parse-ide-mode
+       (cond
+        ((minusp tt)
+         (js2-record-face 'js2-error token))
+        ((setq face (aref js2-kwd-tokens tt))
+         (js2-record-face face token))
+        ((and (= tt js2-NAME)
+              (equal (js2-token-string token) "undefined"))
+         (js2-record-face 'font-lock-constant-face token))))
+     tt))
+ 
+ (defun js2-get-token-internal-1 ()
+   "Return next JavaScript token type, an int such as js2-RETURN.
+ During operation, creates an instance of `js2-token' struct, sets
+ its relevant fields and puts it into `js2-ti-tokens'."
    (let (c c1 identifier-start is-unicode-escape-start
 -        contains-escape escape-val escape-start str result base
 +        contains-escape escape-val str result base
-         is-integer quote-char val look-for-slash continue)
+         is-integer quote-char val look-for-slash continue tt
+         (token (js2-new-token 0)))
+    (setq
+     tt
      (catch 'return
        (while t
          ;; Eat whitespace, possibly sensitive to newlines.
@@@ -5568,10 -5670,9 +5674,10 @@@
                         (setq c val)))))
                  (js2-add-to-string c)
                  (setq c (js2-get-char)))))
-           (setq js2-ts-string (js2-get-string-from-buffer))
+           (js2-set-string-from-buffer token)
            (throw 'return js2-STRING))
-         (js2-ts-return
 -        (case c
++        (js2-ts-return token
 +         (case c
            (?\;
             (throw 'return js2-SEMI))
            (?\[
@@@ -5620,9 -5722,11 +5726,11 @@@
            (?=
             (if (js2-match-char ?=)
                 (if (js2-match-char ?=)
 -                   (js2-ts-return token js2-SHEQ)
 +                   js2-SHEQ
                   (throw 'return js2-EQ))
-              (throw 'return js2-ASSIGN)))
+              (if (js2-match-char ?>)
+                  (js2-ts-return token js2-ARROW)
+                (throw 'return js2-ASSIGN))))
            (?!
             (if (js2-match-char ?=)
                 (if (js2-match-char ?=)
@@@ -5691,23 -5795,23 +5799,23 @@@
                   (setq look-for-slash t))
                  ((eq c ?/)
                   (if look-for-slash
-                    (js2-ts-return js2-COMMENT)))
+                      (js2-ts-return token js2-COMMENT)))
                  (t
-                  (setq look-for-slash nil
-                        js2-token-end js2-ts-cursor)))))
+                  (setf look-for-slash nil
+                        (js2-token-end token) js2-ts-cursor)))))
             (if (js2-match-char ?=)
 -               (js2-ts-return token js2-ASSIGN_DIV)
 +               js2-ASSIGN_DIV
               (throw 'return js2-DIV)))
            (?#
-             (when js2-skip-preprocessor-directives
-               (js2-skip-line)
-               (setq js2-ts-comment-type 'preprocessor
-                     js2-token-end js2-ts-cursor)
-               (throw 'return js2-COMMENT))
-             (throw 'return js2-ERROR))
+            (when js2-skip-preprocessor-directives
+              (js2-skip-line)
+              (setf (js2-token-comment-type token) 'preprocessor
+                    (js2-token-end token) js2-ts-cursor)
+              (throw 'return js2-COMMENT))
+            (throw 'return js2-ERROR))
            (?%
             (if (js2-match-char ?=)
 -               (js2-ts-return token js2-ASSIGN_MOD)
 +               js2-ASSIGN_MOD
               (throw 'return js2-MOD)))
            (?~
             (throw 'return js2-BITNOT))
@@@ -5733,11 -5837,13 +5841,13 @@@
              (t
               (setq c js2-SUB)))
             (setq js2-ts-dirty-line t)
 -           (js2-ts-return token c))
 +           c)
            (otherwise
-            (js2-report-scan-error "msg.illegal.character"))))))))
 -           (js2-report-scan-error "msg.illegal.character"))))))
++           (js2-report-scan-error "msg.illegal.character")))))))
+    (setf (js2-token-type token) tt)
+    token))
  
- (defun js2-read-regexp (start-token)
+ (defun js2-read-regexp (start-tt)
    "Called by parser when it gets / or /= in literal context."
    (let (c err
          in-class  ; inside a '[' .. ']' character-class
@@@ -7248,10 -7302,14 +7303,14 @@@ NODE is either `js2-array-node', `js2-o
     (t (js2-report-error "msg.no.parm" nil (js2-node-abs-pos node)
                          (js2-node-len node)))))
  
- (defun js2-parse-function-params (fn-node pos)
+ (defun js2-parse-function-params (function-type fn-node pos)
    (if (js2-match-token js2-RP)
-       (setf (js2-function-node-rp fn-node) (- js2-token-beg pos))
-     (let (params param default-found rest-param-at)
+       (setf (js2-function-node-rp fn-node) (- (js2-current-token-beg) pos))
+     (let ((paren-free-arrow (and (eq function-type 'FUNCTION_ARROW)
+                                  (eq (js2-current-token-type) js2-NAME)))
 -          params len param default-found rest-param-at)
++          params param default-found rest-param-at)
+       (when paren-free-arrow
+         (js2-unget-token))
        (loop for tt = (js2-peek-token)
              do
              (cond
@@@ -7516,8 -7575,9 +7576,8 @@@ node are given relative start position
    (list js2-ERROR js2-EOF js2-RC))
  
  (defun js2-statement-helper ()
-   (let* ((tt (js2-peek-token))
+   (let* ((tt (js2-get-token))
           (first-tt tt)
 -         (beg (js2-current-token-beg))
           (parser (if (= tt js2-ERROR)
                       #'js2-parse-semi
                     (aref js2-parsers tt)))
@@@ -7597,13 -7656,12 +7656,12 @@@ Return value is a list (EXPR LP RP), wi
      pn))
  
  (defun js2-parse-switch ()
 -  "Parser for if-statement.  Last matched token must be js2-SWITCH."
 +  "Parser for switch-statement.  Last matched token must be js2-SWITCH."
-   (let ((pos js2-token-beg)
+   (let ((pos (js2-current-token-beg))
          tt pn discriminant has-default case-expr case-node
 -        case-pos cases stmt lp rp)
 +        case-pos cases stmt lp)
-     (js2-consume-token)
      (if (js2-must-match js2-LP "msg.no.paren.switch")
-         (setq lp js2-token-beg))
+         (setq lp (js2-current-token-beg)))
      (setq discriminant (js2-parse-expr)
            pn (make-js2-switch-node :discriminant discriminant
                                     :pos pos
@@@ -8149,9 -8195,8 +8195,8 @@@ Current token type is `js2-SEMI' or `js
  
  (defun js2-parse-default-xml-namespace ()
    "Parse a `default xml namespace = <expr>' e4x statement."
-   (let ((pos js2-token-beg)
+   (let ((pos (js2-current-token-beg))
 -        end len expr unary es)
 +        end len expr unary)
-     (js2-consume-token)
      (js2-must-have-xml)
      (js2-set-requires-activation)
      (setq len (- js2-ts-cursor pos))
@@@ -8199,15 -8244,15 +8244,14 @@@ Called when we found a name in a statem
  up any following labels and the next non-label statement into a
  `js2-labeled-stmt-node' bundle and return that.  Otherwise we parse an
  expression and return it wrapped in a `js2-expr-stmt-node'."
-   (let ((pos js2-token-beg)
+   (let ((pos (js2-current-token-beg))
          expr stmt pn bundle
          (continue t))
      ;; set check for label and call down to `js2-parse-primary-expr'
-     (js2-set-check-for-label)
-     (setq expr (js2-parse-expr))
-     (if (/= (js2-node-type expr) js2-LABEL)
-         ;; Parsed non-label expression - wrap with expression stmt.
-         (setq pn (js2-wrap-with-expr-stmt pos expr t)) ;FIXME: `pn' is unused!
+     (setq expr (js2-maybe-parse-label))
+     (if (null expr)
+         ;; Parse the non-label expression and wrap with expression stmt.
 -        (setq pn (js2-wrap-with-expr-stmt pos (js2-parse-expr) t))
++        (setq pn (js2-wrap-with-expr-stmt pos (js2-parse-expr) t)) ;FIXME: 
`pn' is unused!
        ;; else parsed a label
        (setq bundle (make-js2-labeled-stmt-node :pos pos))
        (js2-record-label expr bundle)
@@@ -8754,8 -8833,8 +8832,8 @@@ Returns the list in reverse order.  Con
        result)))
  
  (defun js2-parse-member-expr (&optional allow-call-syntax)
-   (let ((tt (js2-peek-token))
+   (let ((tt (js2-current-token-type))
 -        pn pos target args beg end init tail)
 +        pn pos target args beg end init)
      (if (/= tt js2-NEW)
          (setq pn (js2-parse-primary-expr))
        ;; parse a 'new' expression
@@@ -8792,10 -8871,11 +8870,10 @@@
  Includes parsing for E4X operators like `..' and `.@'.
  If ALLOW-CALL-SYNTAX is nil, stops when we encounter a left-paren.
  Returns an expression tree that includes PN, the parent node."
 -  (let ((beg (js2-node-pos pn))
 -        tt
 +  (let (tt
          (continue t))
      (while continue
-       (setq tt (js2-peek-token))
+       (setq tt (js2-get-token))
        (cond
         ((or (= tt js2-DOT) (= tt js2-DOTDOT))
          (setq pn (js2-parse-property-access tt pn)))
@@@ -8970,10 -9043,10 +9041,10 @@@ MEMBER-TYPE-FLAGS is a bit set trackin
  Returns a `js2-xml-ref-node' if it's an attribute access, a child of a '..'
  operator, or the name is followed by ::.  For a plain name, returns a
  `js2-name-node'.  Returns a `js2-error-node' for malformed XML expressions."
-   (let ((pos (or at-pos js2-token-beg))
+   (let ((pos (or at-pos (js2-current-token-beg)))
          colon-pos
-         (name (js2-create-name-node t js2-current-token))
+         (name (js2-create-name-node t (js2-current-token-type) s))
 -        ns tt ref pn)
 +        ns tt pn)
      (catch 'return
        (when (js2-match-token js2-COLONCOLON)
          (setq ns name
@@@ -9105,32 -9196,15 +9194,15 @@@ array-literals, array comprehensions an
        (js2-report-error "msg.syntax")
        (make-js2-error-node)))))
  
- (defun js2-parse-name (tt-flagged _tt)
-   (let ((name js2-ts-string)
-         (name-pos js2-token-beg)
 -(defun js2-parse-name (tt)
++(defun js2-parse-name (_tt)
+   (let ((name (js2-current-token-string))
          node)
-     (if (and (js2-flag-set-p tt-flagged js2-ti-check-label)
-              (= (js2-peek-token) js2-COLON))
-         (prog1
-             ;; Do not consume colon, it is used as unwind indicator
-             ;; to return to statementHelper.
-             (make-js2-label-node :pos name-pos
-                                  :len (- js2-token-end name-pos)
-                                  :name name)
-           (js2-set-face name-pos
-                         js2-token-end
-                         'font-lock-variable-name-face 'record))
-       ;; Otherwise not a label, just a name.  Unfortunately peeking
-       ;; the next token to check for a colon has biffed js2-token-beg
-       ;; and js2-token-end.  We store the name's bounds in buffer vars
-       ;; and `js2-create-name-node' uses them.
-       (js2-save-name-token-data name-pos name)
-       (setq node (if js2-compiler-xml-available
-                      (js2-parse-property-name nil name 0)
-                    (js2-create-name-node 'check-activation)))
-       (if js2-highlight-external-variables
-           (js2-record-name-node node))
-       node)))
+     (setq node (if js2-compiler-xml-available
+                    (js2-parse-property-name nil name 0)
+                  (js2-create-name-node 'check-activation nil name)))
+     (if js2-highlight-external-variables
+         (js2-record-name-node node))
+     node))
  
  (defun js2-parse-warn-trailing-comma (msg pos elems comma-pos)
    (js2-add-strict-warning
@@@ -9146,7 -9220,8 +9218,7 @@@
     comma-pos))
  
  (defun js2-parse-array-literal ()
-   (let ((pos js2-token-beg)
+   (let ((pos (js2-current-token-beg))
 -        (end (js2-current-token-end))
          (after-lb-or-comma t)
          after-comma tt elems pn
          (continue t))
@@@ -9166,9 -9240,9 +9237,8 @@@
         ((or (= tt js2-RB)
              (= tt js2-EOF))  ; prevent infinite loop
          (if (= tt js2-EOF)
-             (js2-report-error "msg.no.bracket.arg" nil pos)
-           (js2-consume-token))
+             (js2-report-error "msg.no.bracket.arg" nil pos))
          (setq continue nil
 -              end (js2-current-token-end)
                pn (make-js2-array-node :pos pos
                                        :len (- js2-ts-cursor pos)
                                        :elems (nreverse elems)))
@@@ -9215,8 -9290,8 +9286,8 @@@
  EXPR is the first expression after the opening left-bracket.
  POS is the beginning of the LB token preceding EXPR.
  We should have just parsed the 'for' keyword before calling this function."
 -  (let (loops loop first prev filter if-pos result)
 +  (let (loops loop first filter if-pos result)
-     (while (= (js2-peek-token) js2-FOR)
+     (while (= (js2-get-token) js2-FOR)
        (let ((prev (car loops))) ; rearrange scope chain
          (push (setq loop (js2-parse-array-comp-loop)) loops)
          (if prev ; each loop is parent scope to the next one

commit 88509ff9db6416ba257c0ab1d02494bc42b1fbaf
Author: Dmitry Gutov <address@hidden>
Date:   Tue Oct 8 04:50:48 2013 +0300

    Check in .dir-locals.el

diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 0000000..db35973
--- /dev/null
+++ b/.dir-locals.el
@@ -0,0 +1 @@
+((nil . ((sentence-end-double-space . t))))

commit 962d8d58c97b7f6712bcbb9ed9d5935fa1439781
Author: Dmitry Gutov <address@hidden>
Date:   Tue Sep 24 13:41:21 2013 +0300

    js2-parse-switch: Fix the docstring

diff --git a/js2-mode.el b/js2-mode.el
index b193534..ed92e63 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -7597,7 +7597,7 @@ Return value is a list (EXPR LP RP), with absolute paren 
positions."
     pn))
 
 (defun js2-parse-switch ()
-  "Parser for if-statement.  Last matched token must be js2-SWITCH."
+  "Parser for switch-statement.  Last matched token must be js2-SWITCH."
   (let ((pos js2-token-beg)
         tt pn discriminant has-default case-expr case-node
         case-pos cases stmt lp)

commit de0fe6d946df2387e06a799916b23d9abbdb8ffe
Author: Dmitry Gutov <address@hidden>
Date:   Fri Sep 20 04:29:17 2013 +0300

    Fix #114

diff --git a/js2-mode.el b/js2-mode.el
index 14fc8f2..b193534 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -10985,64 +10985,64 @@ move backward across N balanced expressions."
   (setq arg (or arg 1))
   (save-restriction
     (widen) ;; `blink-matching-open' calls `narrow-to-region'
-    (js2-reparse))
-  (let (forward-sexp-function
-        node (start (point)) pos lp rp child)
-    (cond
-     ;; backward-sexp
-     ;; could probably make this better for some cases:
-     ;;  - if in statement block (e.g. function body), go to parent
-     ;;  - infix exprs like (foo in bar) - maybe go to beginning
-     ;;    of infix expr if in the right-side expression?
-     ((and arg (minusp arg))
-      (dotimes (_ (- arg))
-        (js2-backward-sws)
-        (forward-char -1)   ; Enter the node we backed up to.
-        (when (setq node (js2-node-at-point (point) t))
-          (setq pos (js2-node-abs-pos node))
-          (let ((parens (js2-mode-forward-sexp-parens node pos)))
-            (setq lp (car parens)
-                  rp (cdr parens)))
-          (when (and lp (> start lp))
-            (if (and rp (<= start rp))
-                ;; Between parens, check if there's a child node we can jump.
-                (when (setq child (js2-node-closest-child node (point) lp t))
-                  (setq pos (js2-node-abs-pos child)))
-              ;; Before both parens.
-              (setq pos lp)))
-          (let ((state (parse-partial-sexp start pos)))
-            (goto-char (if (not (zerop (car state)))
-                           ;; Stumble at the unbalanced paren if < 0, or
-                           ;; jump a bit further if > 0.
-                           (scan-sexps start -1)
-                         pos))))
-        (unless pos (goto-char (point-min)))))
-     (t
-      ;; forward-sexp
-      (dotimes (_ arg)
-        (js2-forward-sws)
-        (when (setq node (js2-node-at-point (point) t))
-          (setq pos (js2-node-abs-pos node))
-          (let ((parens (js2-mode-forward-sexp-parens node pos)))
-            (setq lp (car parens)
-                  rp (cdr parens)))
-          (or
-           (when (and rp (<= start rp))
-             (if (> start lp)
-                 (when (setq child (js2-node-closest-child node (point) rp))
-                   (setq pos (js2-node-abs-end child)))
-               (setq pos (1+ rp))))
-           ;; No parens or child nodes, looks for the end of the curren node.
-           (incf pos (js2-node-len
-                      (if (js2-expr-stmt-node-p (js2-node-parent node))
-                          ;; Stop after the semicolon.
-                          (js2-node-parent node)
-                        node))))
-          (let ((state (save-excursion (parse-partial-sexp start pos))))
-            (goto-char (if (not (zerop (car state)))
-                           (scan-sexps start 1)
-                         pos))))
-        (unless pos (goto-char (point-max))))))))
+    (js2-reparse)
+    (let (forward-sexp-function
+          node (start (point)) pos lp rp child)
+      (cond
+       ;; backward-sexp
+       ;; could probably make this better for some cases:
+       ;;  - if in statement block (e.g. function body), go to parent
+       ;;  - infix exprs like (foo in bar) - maybe go to beginning
+       ;;    of infix expr if in the right-side expression?
+       ((and arg (minusp arg))
+        (dotimes (_ (- arg))
+          (js2-backward-sws)
+          (forward-char -1)   ; Enter the node we backed up to.
+          (when (setq node (js2-node-at-point (point) t))
+            (setq pos (js2-node-abs-pos node))
+            (let ((parens (js2-mode-forward-sexp-parens node pos)))
+              (setq lp (car parens)
+                    rp (cdr parens)))
+            (when (and lp (> start lp))
+              (if (and rp (<= start rp))
+                  ;; Between parens, check if there's a child node we can jump.
+                  (when (setq child (js2-node-closest-child node (point) lp t))
+                    (setq pos (js2-node-abs-pos child)))
+                ;; Before both parens.
+                (setq pos lp)))
+            (let ((state (parse-partial-sexp start pos)))
+              (goto-char (if (not (zerop (car state)))
+                             ;; Stumble at the unbalanced paren if < 0, or
+                             ;; jump a bit further if > 0.
+                             (scan-sexps start -1)
+                           pos))))
+          (unless pos (goto-char (point-min)))))
+       (t
+        ;; forward-sexp
+        (dotimes (_ arg)
+          (js2-forward-sws)
+          (when (setq node (js2-node-at-point (point) t))
+            (setq pos (js2-node-abs-pos node))
+            (let ((parens (js2-mode-forward-sexp-parens node pos)))
+              (setq lp (car parens)
+                    rp (cdr parens)))
+            (or
+             (when (and rp (<= start rp))
+               (if (> start lp)
+                   (when (setq child (js2-node-closest-child node (point) rp))
+                     (setq pos (js2-node-abs-end child)))
+                 (setq pos (1+ rp))))
+             ;; No parens or child nodes, looks for the end of the curren node.
+             (incf pos (js2-node-len
+                        (if (js2-expr-stmt-node-p (js2-node-parent node))
+                            ;; Stop after the semicolon.
+                            (js2-node-parent node)
+                          node))))
+            (let ((state (save-excursion (parse-partial-sexp start pos))))
+              (goto-char (if (not (zerop (car state)))
+                             (scan-sexps start 1)
+                           pos))))
+          (unless pos (goto-char (point-max)))))))))
 
 (defun js2-mode-forward-sexp-parens (node abs-pos)
   "Return a cons cell with positions of main parens in NODE."

commit c6aada7da653d7f1628bd81f29bd4bd2967daf77
Author: Dmitry Gutov <address@hidden>
Date:   Fri Sep 13 06:26:35 2013 +0300

    New option: js2-indent-switch-body
    
    Closes #111

diff --git a/js2-mode.el b/js2-mode.el
index 505c177..14fc8f2 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -271,6 +271,14 @@ lines, it won't be indented additionally:
   :type 'symbol)
 (js2-mark-safe-local 'js2-pretty-multiline-declarations 'symbolp)
 
+(defcustom js2-indent-switch-body nil
+  "When nil, case labels are indented on the same level as the
+containing switch statement.  Otherwise, all lines inside
+switch statement body are indented one additional level."
+  :type 'boolean
+  :group 'js2-mode)
+(js2-mark-safe-local 'js2-indent-case-same-as-switch 'booleanp)
+
 (defcustom js2-idle-timer-delay 0.2
   "Delay in secs before re-parsing after user makes changes.
 Multiplied by `js2-dynamic-idle-timer-adjust', which see."
@@ -9721,13 +9729,15 @@ In particular, return the buffer position of the first 
`for' kwd."
   "Return the proper indentation for the current line."
   (save-excursion
     (back-to-indentation)
-    (let ((ctrl-stmt-indent (js2-ctrl-statement-indentation))
-          (same-indent-p (looking-at "[]})]\\|\\<case\\>\\|\\<default\\>"))
-          (continued-expr-p (js2-continued-expression-p))
-          (declaration-indent (and js2-pretty-multiline-declarations
-                                   (js2-multiline-decl-indentation)))
-          (bracket (nth 1 parse-status))
-          beg)
+    (let* ((ctrl-stmt-indent (js2-ctrl-statement-indentation))
+           (at-closing-bracket (looking-at "[]})]"))
+           (same-indent-p (or at-closing-bracket
+                              (looking-at "\\<case\\>\\|\\<default\\>")))
+           (continued-expr-p (js2-continued-expression-p))
+           (declaration-indent (and js2-pretty-multiline-declarations
+                                    (js2-multiline-decl-indentation)))
+           (bracket (nth 1 parse-status))
+           beg indent)
       (cond
        ;; indent array comprehension continuation lines specially
        ((and bracket
@@ -9757,12 +9767,18 @@ In particular, return the buffer position of the first 
`for' kwd."
           (and (eq js2-pretty-multiline-declarations 'all)
                (looking-at js2-declaration-keyword-re)
                (goto-char (1+ (match-end 0))))
-          (cond (same-indent-p
-                 (current-column))
-                (continued-expr-p
-                 (+ (current-column) (* 2 js2-basic-offset)))
-                (t
-                 (+ (current-column) js2-basic-offset))))
+          (setq indent
+                (cond (same-indent-p
+                       (current-column))
+                      (continued-expr-p
+                       (+ (current-column) (* 2 js2-basic-offset)))
+                      (t
+                       (+ (current-column) js2-basic-offset))))
+          (if (and js2-indent-switch-body
+                   (not at-closing-bracket)
+                   (looking-at "\\_<switch\\_>"))
+              (+ indent js2-basic-offset)
+            indent))
          (t
           (unless same-indent-p
             (forward-char)

commit 7add7efbd54fe39b78752c2e2e74251edc3583ef
Author: Stefan Monnier <address@hidden>
Date:   Fri Sep 6 05:47:32 2013 +0300

    * packages/js2-mode/js2-mode.el: Remove unused variables.  Use posix
    character classes.  Do a bit of CSE simplification.
    (js2-parse-highlight-member-expr-node): Flip test order to simplify code.
    (js2-re-search-forward, js2-re-search-backward): Don't quote code.
    (js2-echo-help): Defalias applies to symbol, not functions.

diff --git a/js2-mode.el b/js2-mode.el
index dd53a84..505c177 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -1126,8 +1126,7 @@ information."
   :group 'js2-mode)
 
 (defvar js2-mode-map
-  (let ((map (make-sparse-keymap))
-        keys)
+  (let ((map (make-sparse-keymap)))
     (define-key map [mouse-1] #'js2-mode-show-node)
     (define-key map (kbd "M-j") #'js2-line-break)
     (define-key map (kbd "C-c C-e") #'js2-mode-hide-element)
@@ -1202,7 +1201,7 @@ information."
     map)
   "Keymap used in `js2-mode' buffers.")
 
-(defconst js2-mode-identifier-re "[a-zA-Z_$][a-zA-Z0-9_$]*")
+(defconst js2-mode-identifier-re "[[:alpha:]_$][[:alnum:]_$]*")
 
 (defvar js2-mode-//-comment-re "^\\(\\s-*\\)//.+"
   "Matches a //-comment line.  Must be first non-whitespace on line.
@@ -1225,7 +1224,7 @@ First match-group is the leading whitespace.")
 (js2-deflocal js2-imenu-function-map nil "Private variable")
 
 (defvar js2-paragraph-start
-  "\\(@[a-zA-Z]+\\>\\|$\\)")
+  "\\(@[[:alpha:]]+\\>\\|$\\)")
 
 ;; Note that we also set a 'c-in-sws text property in html comments,
 ;; so that `c-forward-sws' and `c-backward-sws' work properly.
@@ -2282,6 +2281,7 @@ NAME can be a Lisp symbol or string.  SYMBOL is a 
`js2-symbol'."
             (:constructor make-js2-script-node (&key (type js2-SCRIPT)
                                                      (pos js2-token-beg)
                                                      len
+                                                     ;; FIXME: What are those?
                                                      var-decls
                                                      fun-decls)))
   functions   ; Lisp list of nested functions
@@ -2500,9 +2500,7 @@ NAME can be a Lisp symbol or string.  SYMBOL is a 
`js2-symbol'."
         (insert "each "))
     (insert "(")
     (js2-print-ast (js2-for-in-node-iterator n) 0)
-    (if forof
-        (insert " of ")
-      (insert " in "))
+    (insert (if forof " of " " in "))
     (js2-print-ast (js2-for-in-node-object n) 0)
     (insert ") {\n")
     (js2-print-body (js2-for-in-node-body n) (1+ i))
@@ -3670,14 +3668,13 @@ as opposed to required parens such as those enclosing 
an if-conditional."
   (js2-visit-ast (js2-array-comp-loop-node-iterator n) v)
   (js2-visit-ast (js2-array-comp-loop-node-object n) v))
 
-(defun js2-print-array-comp-loop (n i)
+(defun js2-print-array-comp-loop (n _i)
   (insert "for ")
   (when (js2-array-comp-loop-node-foreach-p n) (insert "each "))
   (insert "(")
   (js2-print-ast (js2-array-comp-loop-node-iterator n) 0)
-  (if (js2-array-comp-loop-node-forof-p n)
-      (insert " of ")
-    (insert " in "))
+  (insert (if (js2-array-comp-loop-node-forof-p n)
+              " of " " in "))
   (js2-print-ast (js2-array-comp-loop-node-object n) 0)
   (insert ")"))
 
@@ -4285,9 +4282,9 @@ Returns nil if no applicable child is found."
   (let ((kids (if (js2-function-node-p parent)
                   (js2-block-node-kids (js2-function-node-body parent))
                 (js2-node-child-list parent)))
-        (beg (if (js2-function-node-p parent)
-                 (js2-node-abs-pos (js2-function-node-body parent))
-               (js2-node-abs-pos parent)))
+        (beg (js2-node-abs-pos (if (js2-function-node-p parent)
+                                   (js2-function-node-body parent)
+                                 parent)))
         kid result fn
         (continue t))
     (setq fn (if after '>= '<))
@@ -4295,8 +4292,8 @@ Returns nil if no applicable child is found."
       (setq kid (car kids))
       (if (funcall fn (+ beg (js2-node-pos kid)) pos)
           (setq result kid
-                continue (if after nil t))
-        (setq continue (if after t nil)))
+                continue (not after))
+        (setq continue after))
       (setq kids (cdr kids)))
     result))
 
@@ -4503,11 +4500,11 @@ If NODE is the ast-root, returns nil."
 
 ;;; visitor infrastructure
 
-(defun js2-visit-none (node callback)
+(defun js2-visit-none (_node _callback)
   "Visitor for AST node that have no node children."
   nil)
 
-(defun js2-print-none (node indent)
+(defun js2-print-none (_node _indent)
   "Visitor for AST node with no printed representation.")
 
 (defun js2-print-body (node indent)
@@ -4537,8 +4534,7 @@ Makes `js2-ast-parent-nodes' available to the printer 
functions."
 Requires `js2-ast-parent-nodes' to be non-nil.
 You should use `js2-print-tree' instead of this function."
   (let ((printer (get (aref node 0) 'js2-printer))
-        (i (or indent 0))
-        (pos (js2-node-abs-pos node)))
+        (i (or indent 0)))
     ;; TODO:  wedge comments in here somewhere
     (if printer
         (funcall printer node i))))
@@ -4970,7 +4966,7 @@ nor always false."
 (defconst js2-token-names
   (let* ((names (make-vector js2-num-tokens -1))
          (case-fold-search nil)  ; only match js2-UPPER_CASE
-         (syms (apropos-internal "^js2-\\(?:[A-Z_]+\\)")))
+         (syms (apropos-internal "^js2-\\(?:[[:upper:]_]+\\)")))
     (loop for sym in syms
           for i from 0
           do
@@ -5080,7 +5076,7 @@ Doesn't change the values of any scanner variables."
   (ignore-errors
     (let ((s (buffer-substring-no-properties js2-ts-cursor
                                              (+ 4 js2-ts-cursor))))
-      (if (string-match "[a-zA-Z0-9]\\{4\\}" s)
+      (if (string-match "[[:alnum:]]\\{4\\}" s)
           (read (concat "?\\u" s))))))
 
 (defun js2-match-char (test)
@@ -5132,10 +5128,9 @@ Returns nil and consumes nothing if TEST is not the next 
character."
 
 (defun js2-skip-line ()
   "Skip to end of line."
-  (let (c)
-    (while (not (memq (setq c (js2-get-char)) js2-eol-chars)))
-    (js2-unget-char)
-    (setq js2-token-end js2-ts-cursor)))
+  (while (not (memq (js2-get-char) js2-eol-chars)))
+  (js2-unget-char)
+  (setq js2-token-end js2-ts-cursor))
 
 (defun js2-init-scanner (&optional buf line)
   "Create token stream for BUF starting on LINE.
@@ -5316,7 +5311,7 @@ corresponding number.  Otherwise return -1."
 (defun js2-get-token ()
   "Return next JavaScript token, an int such as js2-RETURN."
   (let (c c1 identifier-start is-unicode-escape-start
-        contains-escape escape-val escape-start str result base
+        contains-escape escape-val str result base
         is-integer quote-char val look-for-slash continue)
     (catch 'return
       (while t
@@ -5369,7 +5364,7 @@ corresponding number.  Otherwise return -1."
                   ;; an error here.
                   (progn
                     (setq escape-val 0)
-                    (dotimes (i 4)
+                    (dotimes (_ 4)
                       (setq c (js2-get-char)
                             escape-val (js2-x-digit-to-int c escape-val))
                       ;; Next check takes care of c < 0 and bad escape
@@ -5503,7 +5498,7 @@ corresponding number.  Otherwise return -1."
                                ;; just copy the string in IDE-mode
                                (js2-add-to-string ?\\)
                                (js2-add-to-string ?u)
-                               (dotimes (i 3)
+                               (dotimes (_ 3)
                                  (js2-add-to-string (js2-get-char)))
                                (setq c (js2-get-char))) ; added at end of loop
                            ;; flag it as an invalid escape
@@ -5514,7 +5509,7 @@ corresponding number.  Otherwise return -1."
                        ;; literal character sequence that follows.
                        (js2-add-to-string ?u)
                        (setq escape-val 0)
-                       (dotimes (i 4)
+                       (dotimes (_ 4)
                          (setq c (js2-get-char)
                                escape-val (js2-x-digit-to-int c escape-val))
                          (if (minusp escape-val)
@@ -5567,7 +5562,8 @@ corresponding number.  Otherwise return -1."
                 (setq c (js2-get-char)))))
           (setq js2-ts-string (js2-get-string-from-buffer))
           (throw 'return js2-STRING))
-        (case c
+        (js2-ts-return
+         (case c
           (?\;
            (throw 'return js2-SEMI))
           (?\[
@@ -5588,43 +5584,42 @@ corresponding number.  Otherwise return -1."
            (throw 'return js2-HOOK))
           (?:
            (if (js2-match-char ?:)
-               (js2-ts-return js2-COLONCOLON)
+               js2-COLONCOLON
              (throw 'return js2-COLON)))
           (?.
            (if (js2-match-char ?.)
                (if (js2-match-char ?.)
-                   (js2-ts-return js2-TRIPLEDOT)
-                 (js2-ts-return js2-DOTDOT))
+                   js2-TRIPLEDOT js2-DOTDOT)
              (if (js2-match-char ?\()
-                 (js2-ts-return js2-DOTQUERY)
+                 js2-DOTQUERY
                (throw 'return js2-DOT))))
           (?|
            (if (js2-match-char ?|)
                (throw 'return js2-OR)
              (if (js2-match-char ?=)
-                 (js2-ts-return js2-ASSIGN_BITOR)
+                 js2-ASSIGN_BITOR
                (throw 'return js2-BITOR))))
           (?^
            (if (js2-match-char ?=)
-               (js2-ts-return js2-ASSIGN_BITOR)
+               js2-ASSIGN_BITOR
              (throw 'return js2-BITXOR)))
           (?&
            (if (js2-match-char ?&)
                (throw 'return js2-AND)
              (if (js2-match-char ?=)
-                 (js2-ts-return js2-ASSIGN_BITAND)
+                 js2-ASSIGN_BITAND
                (throw 'return js2-BITAND))))
           (?=
            (if (js2-match-char ?=)
                (if (js2-match-char ?=)
-                   (js2-ts-return js2-SHEQ)
+                   js2-SHEQ
                  (throw 'return js2-EQ))
              (throw 'return js2-ASSIGN)))
           (?!
            (if (js2-match-char ?=)
                (if (js2-match-char ?=)
-                   (js2-ts-return js2-SHNE)
-                 (js2-ts-return js2-NE))
+                   js2-SHNE
+                 js2-NE)
              (throw 'return js2-NOT)))
           (?<
            ;; NB:treat HTML begin-comment as comment-till-eol
@@ -5637,26 +5632,26 @@ corresponding number.  Otherwise return -1."
              (js2-unget-char))
            (if (js2-match-char ?<)
                (if (js2-match-char ?=)
-                   (js2-ts-return js2-ASSIGN_LSH)
-                 (js2-ts-return js2-LSH))
+                   js2-ASSIGN_LSH
+                 js2-LSH)
              (if (js2-match-char ?=)
-                 (js2-ts-return js2-LE)
+                 js2-LE
                (throw 'return js2-LT))))
           (?>
            (if (js2-match-char ?>)
                (if (js2-match-char ?>)
                    (if (js2-match-char ?=)
-                       (js2-ts-return js2-ASSIGN_URSH)
-                     (js2-ts-return js2-URSH))
+                       js2-ASSIGN_URSH
+                     js2-URSH)
                  (if (js2-match-char ?=)
-                     (js2-ts-return js2-ASSIGN_RSH)
-                   (js2-ts-return js2-RSH)))
+                     js2-ASSIGN_RSH
+                   js2-RSH))
              (if (js2-match-char ?=)
-                 (js2-ts-return js2-GE)
+                 js2-GE
                (throw 'return js2-GT))))
           (?*
            (if (js2-match-char ?=)
-               (js2-ts-return js2-ASSIGN_MUL)
+               js2-ASSIGN_MUL
              (throw 'return js2-MUL)))
           (?/
            ;; is it a // comment?
@@ -5693,9 +5688,9 @@ corresponding number.  Otherwise return -1."
                  (setq look-for-slash nil
                        js2-token-end js2-ts-cursor)))))
            (if (js2-match-char ?=)
-               (js2-ts-return js2-ASSIGN_DIV)
+               js2-ASSIGN_DIV
              (throw 'return js2-DIV)))
-           (?#
+          (?#
             (when js2-skip-preprocessor-directives
               (js2-skip-line)
               (setq js2-ts-comment-type 'preprocessor
@@ -5704,15 +5699,15 @@ corresponding number.  Otherwise return -1."
             (throw 'return js2-ERROR))
           (?%
            (if (js2-match-char ?=)
-               (js2-ts-return js2-ASSIGN_MOD)
+               js2-ASSIGN_MOD
              (throw 'return js2-MOD)))
           (?~
            (throw 'return js2-BITNOT))
           (?+
            (if (js2-match-char ?=)
-               (js2-ts-return js2-ASSIGN_ADD)
+               js2-ASSIGN_ADD
              (if (js2-match-char ?+)
-                 (js2-ts-return js2-INC)
+                 js2-INC
                (throw 'return js2-ADD))))
           (?-
            (cond
@@ -5730,9 +5725,9 @@ corresponding number.  Otherwise return -1."
             (t
              (setq c js2-SUB)))
            (setq js2-ts-dirty-line t)
-           (js2-ts-return c))
+           c)
           (otherwise
-           (js2-report-scan-error "msg.illegal.character")))))))
+           (js2-report-scan-error "msg.illegal.character"))))))))
 
 (defun js2-read-regexp (start-token)
   "Called by parser when it gets / or /= in literal context."
@@ -6122,45 +6117,45 @@ Shown at or above `js2-highlight-level' 3.")
                           (js2-name-node-p target)
                           (js2-name-node-name target)))
         (prop-name (if prop (js2-name-node-name prop)))
-        (level1 (>= js2-highlight-level 1))
         (level2 (>= js2-highlight-level 2))
-        (level3 (>= js2-highlight-level 3))
-        pos face)
+        (level3 (>= js2-highlight-level 3)))
     (when level2
-      (if call-p
-          (cond
-           ((and target prop)
-            (cond
-             ((and level3 (string-match js2-ecma-function-props prop-name))
-              (setq face 'font-lock-builtin-face))
-             ((and target-name prop)
-              (cond
-               ((string= target-name "Date")
-                (if (string-match js2-ecma-date-props prop-name)
-                    (setq face 'font-lock-builtin-face)))
-               ((string= target-name "Math")
-                (if (string-match js2-ecma-math-funcs prop-name)
-                    (setq face 'font-lock-builtin-face)))))))
-           (prop
-            (if (string-match js2-ecma-global-funcs prop-name)
-                (setq face 'font-lock-builtin-face))))
-        (cond
-         ((and target prop)
-          (cond
-           ((string= target-name "Number")
-            (if (string-match js2-ecma-number-props prop-name)
-                (setq face 'font-lock-constant-face)))
-           ((string= target-name "Math")
-            (if (string-match js2-ecma-math-props prop-name)
-                (setq face 'font-lock-constant-face)))))
-         (prop
-          (if (string-match js2-ecma-object-props prop-name)
-              (setq face 'font-lock-constant-face)))))
-      (when face
-        (js2-set-face (setq pos (+ (js2-node-pos parent) ; absolute
-                                   (js2-node-pos prop))) ; relative
-                      (+ pos (js2-node-len prop))
-                      face 'record)))))
+      (let ((face
+             (if call-p
+                 (cond
+                  ((and target prop)
+                   (cond
+                    ((and level3 (string-match js2-ecma-function-props 
prop-name))
+                     'font-lock-builtin-face)
+                    ((and target-name prop)
+                     (cond
+                      ((string= target-name "Date")
+                       (if (string-match js2-ecma-date-props prop-name)
+                           'font-lock-builtin-face))
+                      ((string= target-name "Math")
+                       (if (string-match js2-ecma-math-funcs prop-name)
+                           'font-lock-builtin-face))))))
+                  (prop
+                   (if (string-match js2-ecma-global-funcs prop-name)
+                       'font-lock-builtin-face)))
+               (cond
+                ((and target prop)
+                 (cond
+                  ((string= target-name "Number")
+                   (if (string-match js2-ecma-number-props prop-name)
+                       'font-lock-constant-face))
+                  ((string= target-name "Math")
+                   (if (string-match js2-ecma-math-props prop-name)
+                       'font-lock-constant-face))))
+                (prop
+                 (if (string-match js2-ecma-object-props prop-name)
+                     'font-lock-constant-face))))))
+        (when face
+          (let ((pos (+ (js2-node-pos parent)  ; absolute
+                        (js2-node-pos prop)))) ; relative
+            (js2-set-face pos
+                          (+ pos (js2-node-len prop))
+                          face 'record)))))))
 
 (defun js2-parse-highlight-member-expr-node (node)
   "Perform syntax highlighting of EcmaScript built-in properties.
@@ -6195,15 +6190,12 @@ The variable `js2-highlight-level' governs this 
highighting."
         (setq target (js2-prop-get-node-left node)
               prop (js2-prop-get-node-right node)))
       (cond
-       ((js2-name-node-p target)
-        (if (js2-name-node-p prop)
-            ;; case 2a:  simple target, simple prop name, e.g. foo.bar
-            (js2-parse-highlight-prop-get parent target prop call-p)
-          ;; case 2b:  simple target, complex name, e.g. foo.x[y]
-          (js2-parse-highlight-prop-get parent target nil call-p)))
        ((js2-name-node-p prop)
-        ;; case 2c:  complex target, simple name, e.g. x[y].bar
-        (js2-parse-highlight-prop-get parent target prop call-p)))))))
+        ;; case 2(a&c):  simple or complex target, simple name, e.g. x[y].bar
+        (js2-parse-highlight-prop-get parent target prop call-p))
+       ((js2-name-node-p target)
+        ;; case 2b:  simple target, complex name, e.g. foo.x[y]
+        (js2-parse-highlight-prop-get parent target nil call-p)))))))
 
 (defun js2-parse-highlight-member-expr-fn-name (expr)
   "Highlight the `baz' in function foo.bar.baz(args) {...}.
@@ -6229,7 +6221,7 @@ of a simple name.  Called before EXPR has a parent node."
           "\\(?:param\\|argument\\)"
           "\\)"
           "\\s-*\\({[^}]+}\\)?"         ; optional type
-          "\\s-*\\[?\\([a-zA-Z0-9_$\.]+\\)?\\]?"  ; name
+          "\\s-*\\[?\\([[:alnum:]_$\.]+\\)?\\]?"  ; name
           "\\>")
   "Matches jsdoc tags with optional type and optional param name.")
 
@@ -6330,7 +6322,7 @@ of a simple name.  Called before EXPR has a parent node."
   "Matches a jsdoc @see tag.")
 
 (defconst js2-jsdoc-html-tag-regexp
-  "\\(</?\\)\\([a-zA-Z]+\\)\\s-*\\(/?>\\)"
+  "\\(</?\\)\\([[:alpha:]]+\\)\\s-*\\(/?>\\)"
   "Matches a simple (no attributes) html start- or end-tag.")
 
 (defun js2-jsdoc-highlight-helper ()
@@ -6385,9 +6377,9 @@ of a simple name.  Called before EXPR has a parent node."
                             (match-end 3)
                             'js2-jsdoc-html-tag-delimiter))))))))
 
-(defun js2-highlight-assign-targets (node left right)
+(defun js2-highlight-assign-targets (_node left right)
   "Highlight function properties and external variables."
-  (let (leftpos end name)
+  (let (leftpos name)
     ;; highlight vars and props assigned function values
     (when (js2-function-node-p right)
       (cond
@@ -6407,10 +6399,10 @@ of a simple name.  Called before EXPR has a parent 
node."
 (defun js2-record-name-node (node)
   "Saves NODE to `js2-recorded-identifiers' to check for undeclared variables
 later. NODE must be a name node."
-  (let (leftpos end)
+  (let ((leftpos (js2-node-abs-pos node)))
     (push (list node js2-current-scope
-                (setq leftpos (js2-node-abs-pos node))
-                (setq end (+ leftpos (js2-node-len node))))
+                leftpos
+                (+ leftpos (js2-node-len node)))
           js2-recorded-identifiers)))
 
 (defun js2-highlight-undeclared-vars ()
@@ -6587,7 +6579,7 @@ VAR, if non-nil, is the expression that NODE is being 
assigned to.
 When passed arguments of wrong type, does nothing."
   (when js2-parse-ide-mode
     (let ((fun-p (js2-function-node-p node))
-          qname left fname-node pos)
+          qname fname-node)
       (cond
        ;; non-anonymous function declaration?
        ((and fun-p
@@ -6641,7 +6633,7 @@ e.g. for foo.bar.baz = {...}, QNAME is (foo-node bar-node 
baz-node).
 POS is the absolute position of the node.
 We do a depth-first traversal of NODE.  For any functions we find,
 we append the property name to QNAME, then call `js2-record-imenu-entry'."
-  (let (left right prop-qname)
+  (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.
@@ -6696,7 +6688,7 @@ NODE must be `js2-function-node'."
   "Modify function-declaration name chains after parsing finishes.
 Some of the information is only available after the parse tree is complete.
 For instance, processing a nested scope requires a parent function node."
-  (let (result head fn current-fn parent-qname qname p elem)
+  (let (result fn parent-qname p elem)
     (dolist (entry entries)
       ;; function node goes first
       (destructuring-bind (current-fn &rest (&whole chain head &rest)) entry
@@ -7027,7 +7019,7 @@ Returns t on match, nil if no match."
   (if (js2-function-node-p js2-current-script-or-fn)
       (setf (js2-function-node-needs-activation js2-current-script-or-fn) t)))
 
-(defun js2-check-activation-name (name token)
+(defun js2-check-activation-name (name _token)
   (when (js2-inside-function)
     ;; skip language-version 1.2 check from Rhino
     (if (or (string= "arguments" name)
@@ -7251,7 +7243,7 @@ NODE is either `js2-array-node', `js2-object-node', or 
`js2-name-node'."
 (defun js2-parse-function-params (fn-node pos)
   (if (js2-match-token js2-RP)
       (setf (js2-function-node-rp fn-node) (- js2-token-beg pos))
-    (let (params len param default-found rest-param-at)
+    (let (params param default-found rest-param-at)
       (loop for tt = (js2-peek-token)
             do
             (cond
@@ -7442,7 +7434,7 @@ node are given relative start positions and correct 
lengths."
     pn))
 
 (defun js2-parse-statement ()
-  (let (tt pn beg end)
+  (let (pn beg end)
     ;; coarse-grained user-interrupt check - needs work
     (and js2-parse-interruptable-p
          (zerop (% (incf js2-parse-stmt-count)
@@ -7518,12 +7510,10 @@ node are given relative start positions and correct 
lengths."
 (defun js2-statement-helper ()
   (let* ((tt (js2-peek-token))
          (first-tt tt)
-         (beg js2-token-beg)
          (parser (if (= tt js2-ERROR)
                      #'js2-parse-semi
                    (aref js2-parsers tt)))
-         pn
-         tt-flagged)
+         pn)
     ;; If the statement is set, then it's been told its label by now.
     (and js2-labeled-stmt
          (js2-labeled-stmt-node-stmt js2-labeled-stmt)
@@ -7602,7 +7592,7 @@ Return value is a list (EXPR LP RP), with absolute paren 
positions."
   "Parser for if-statement.  Last matched token must be js2-SWITCH."
   (let ((pos js2-token-beg)
         tt pn discriminant has-default case-expr case-node
-        case-pos cases stmt lp rp)
+        case-pos cases stmt lp)
     (js2-consume-token)
     (if (js2-must-match js2-LP "msg.no.paren.switch")
         (setq lp js2-token-beg))
@@ -8152,7 +8142,7 @@ Last matched token is `js2-SEMI' or `js2-ERROR'."
 (defun js2-parse-default-xml-namespace ()
   "Parse a `default xml namespace = <expr>' e4x statement."
   (let ((pos js2-token-beg)
-        end len expr unary es)
+        end len expr unary)
     (js2-consume-token)
     (js2-must-have-xml)
     (js2-set-requires-activation)
@@ -8202,7 +8192,6 @@ up any following labels and the next non-label statement 
into a
 `js2-labeled-stmt-node' bundle and return that.  Otherwise we parse an
 expression and return it wrapped in a `js2-expr-stmt-node'."
   (let ((pos js2-token-beg)
-        (end js2-token-end)
         expr stmt pn bundle
         (continue t))
     ;; set check for label and call down to `js2-parse-primary-expr'
@@ -8210,7 +8199,7 @@ expression and return it wrapped in a 
`js2-expr-stmt-node'."
     (setq expr (js2-parse-expr))
     (if (/= (js2-node-type expr) js2-LABEL)
         ;; Parsed non-label expression - wrap with expression stmt.
-        (setq pn (js2-wrap-with-expr-stmt pos expr t))
+        (setq pn (js2-wrap-with-expr-stmt pos expr t)) ;FIXME: `pn' is unused!
       ;; else parsed a label
       (setq bundle (make-js2-labeled-stmt-node :pos pos))
       (js2-record-label expr bundle)
@@ -8758,7 +8747,7 @@ Returns the list in reverse order.  Consumes the 
right-paren token."
 
 (defun js2-parse-member-expr (&optional allow-call-syntax)
   (let ((tt (js2-peek-token))
-        pn pos target args beg end init tail)
+        pn pos target args beg end init)
     (if (/= tt js2-NEW)
         (setq pn (js2-parse-primary-expr))
       ;; parse a 'new' expression
@@ -8795,8 +8784,7 @@ Returns the list in reverse order.  Consumes the 
right-paren token."
 Includes parsing for E4X operators like `..' and `.@'.
 If ALLOW-CALL-SYNTAX is nil, stops when we encounter a left-paren.
 Returns an expression tree that includes PN, the parent node."
-  (let ((beg (js2-node-pos pn))
-        tt
+  (let (tt
         (continue t))
     (while continue
       (setq tt (js2-peek-token))
@@ -8964,7 +8952,7 @@ Called if we peeked an '@' token."
       (js2-save-name-token-data js2-token-beg "")
       (js2-parse-property-name js2-token-beg "" 0)))))
 
-(defun js2-parse-property-name (at-pos s member-type-flags)
+(defun js2-parse-property-name (at-pos _s member-type-flags)
   "Check if :: follows name in which case it becomes qualified name.
 
 AT-POS is a natural number if we just read an '@' token, else nil.
@@ -8977,7 +8965,7 @@ operator, or the name is followed by ::.  For a plain 
name, returns a
   (let ((pos (or at-pos js2-token-beg))
         colon-pos
         (name (js2-create-name-node t js2-current-token))
-        ns tt ref pn)
+        ns tt pn)
     (catch 'return
       (when (js2-match-token js2-COLONCOLON)
         (setq ns name
@@ -9109,7 +9097,7 @@ array-literals, array comprehensions and regular 
expressions."
       (js2-report-error "msg.syntax")
       (make-js2-error-node)))))
 
-(defun js2-parse-name (tt-flagged tt)
+(defun js2-parse-name (tt-flagged _tt)
   (let ((name js2-ts-string)
         (name-pos js2-token-beg)
         node)
@@ -9151,7 +9139,6 @@ array-literals, array comprehensions and regular 
expressions."
 
 (defun js2-parse-array-literal ()
   (let ((pos js2-token-beg)
-        (end js2-token-end)
         (after-lb-or-comma t)
         after-comma tt elems pn
         (continue t))
@@ -9174,7 +9161,6 @@ array-literals, array comprehensions and regular 
expressions."
             (js2-report-error "msg.no.bracket.arg" nil pos)
           (js2-consume-token))
         (setq continue nil
-              end js2-token-end
               pn (make-js2-array-node :pos pos
                                       :len (- js2-ts-cursor pos)
                                       :elems (nreverse elems)))
@@ -9221,7 +9207,7 @@ array-literals, array comprehensions and regular 
expressions."
 EXPR is the first expression after the opening left-bracket.
 POS is the beginning of the LB token preceding EXPR.
 We should have just parsed the 'for' keyword before calling this function."
-  (let (loops loop first prev filter if-pos result)
+  (let (loops loop first filter if-pos result)
     (while (= (js2-peek-token) js2-FOR)
       (let ((prev (car loops))) ; rearrange scope chain
         (push (setq loop (js2-parse-array-comp-loop)) loops)
@@ -9538,16 +9524,14 @@ of continued expressions.")
   "Search forward but ignore strings and comments.
 Invokes `re-search-forward' but treats the buffer as if strings
 and comments have been removed."
-  (let ((saved-point (point))
-        (search-expr
-         (cond ((null count)
-                '(js2-re-search-forward-inner regexp bound 1))
-               ((< count 0)
-                '(js2-re-search-backward-inner regexp bound (- count)))
-               ((> count 0)
-                '(js2-re-search-forward-inner regexp bound count)))))
+  (let ((saved-point (point)))
     (condition-case err
-        (eval search-expr)
+        (cond ((null count)
+               (js2-re-search-forward-inner regexp bound 1))
+              ((< count 0)
+               (js2-re-search-backward-inner regexp bound (- count)))
+              ((> count 0)
+               (js2-re-search-forward-inner regexp bound count)))
       (search-failed
        (goto-char saved-point)
        (unless noerror
@@ -9576,16 +9560,14 @@ and comments have been removed."
   "Search backward but ignore strings and comments.
 Invokes `re-search-backward' but treats the buffer as if strings
 and comments have been removed."
-  (let ((saved-point (point))
-        (search-expr
-         (cond ((null count)
-                '(js2-re-search-backward-inner regexp bound 1))
-               ((< count 0)
-                '(js2-re-search-forward-inner regexp bound (- count)))
-               ((> count 0)
-                '(js2-re-search-backward-inner regexp bound count)))))
+  (let ((saved-point (point)))
     (condition-case err
-        (eval search-expr)
+        (cond ((null count)
+               (js2-re-search-backward-inner regexp bound 1))
+              ((< count 0)
+               (js2-re-search-forward-inner regexp bound (- count)))
+              ((> count 0)
+               (js2-re-search-backward-inner regexp bound count)))
       (search-failed
        (goto-char saved-point)
        (unless noerror
@@ -9663,7 +9645,7 @@ to a multiline declaration statement.  See 
`js2-pretty-multiline-declarations'."
                                    (js2-backward-sws))
                                  (not (eq (char-before) ?\;)))
                             (js2-same-line pos)))))
-          (condition-case err
+          (condition-case _
               (backward-sexp)
             (scan-error (setq at-opening-bracket t))))
         (when (looking-at js2-declaration-keyword-re)
@@ -9883,7 +9865,7 @@ in reverse."
                         (forward-line 0)  ; move to bol
                         (1+ (count-lines (point-min) (point)))))
         positions pos main-pos anchor arglist-cont same-indent
-        prev-line-col basic-offset computed-pos)
+        basic-offset computed-pos)
     ;; temporarily don't record undo info, if user requested this
     (when js2-mode-indent-inhibit-undo
       (setq buffer-undo-list t))
@@ -9895,7 +9877,7 @@ in reverse."
                            (back-to-indentation)
                            (js2-backward-sws)
                            (back-to-indentation)
-                           (setq prev-line-col (current-column)))
+                           (current-column))
                          js2-basic-offset))
                 positions)
 
@@ -9906,7 +9888,7 @@ in reverse."
                            (back-to-indentation)
                            (js2-backward-sws)
                            (back-to-indentation)
-                           (setq prev-line-col (current-column)))
+                           (current-column))
                          (* 2 js2-basic-offset)))
                 positions)
 
@@ -10048,7 +10030,7 @@ If so, we don't ever want to use bounce-indent."
 (defun js2-indent-line (&optional bounce-backwards)
   "Indent the current line as JavaScript source text."
   (interactive)
-  (let (parse-status current-indent offset indent-col moved
+  (let (parse-status offset indent-col
         ;; Don't whine about errors/warnings when we're indenting.
         ;; This has to be set before calling parse-partial-sexp below.
         (inhibit-point-motion-hooks t))
@@ -10164,7 +10146,7 @@ highlighting features of `js2-mode'."
         (let ((inhibit-read-only t))
           (erase-buffer)
           (dolist (err all-errs)
-            (destructuring-bind ((msg-key beg end &rest) type line) err
+            (destructuring-bind ((msg-key beg _end &rest) type line) err
               (insert-text-button
                (format "line %d: %s" line (js2-get-msg msg-key))
                'face type
@@ -10367,14 +10349,14 @@ it to be reparsed when the buffer is selected."
                t)
   (js2-reparse))
 
-(defun js2-mode-edit (beg end len)
+(defun js2-mode-edit (_beg _end _len)
   "Schedule a new parse after buffer is edited.
 Buffer edit spans from BEG to END and is of length LEN."
   (setq js2-mode-buffer-dirty-p t)
   (js2-mode-hide-overlay)
   (js2-mode-reset-timer))
 
-(defun js2-minor-mode-edit (beg end len)
+(defun js2-minor-mode-edit (_beg _end _len)
   "Callback for buffer edits in `js2-mode'.
 Schedules a new parse after buffer is edited.
 Buffer edit spans from BEG to END and is of length LEN."
@@ -10452,7 +10434,7 @@ buffer will only rebuild its `js2-mode-ast' if the 
buffer is dirty."
                      (js2-node-short-name (js2-node-parent node))
                    "nil"))))))
 
-(defun js2-mode-hide-overlay (&optional p1 p2)
+(defun js2-mode-hide-overlay (&optional _p1 p2)
   "Remove the debugging overlay when the point moves.
 P1 and P2 are the old and new values of point, respectively."
   (when js2-mode-node-overlay
@@ -10560,7 +10542,7 @@ This ensures that the counts and `next-error' are 
correct."
     (dolist (e (js2-ast-root-warnings js2-mode-ast))
       (js2-mode-show-warn-or-err e 'js2-warning))))
 
-(defun js2-echo-error (old-point new-point)
+(defun js2-echo-error (_old-point new-point)
   "Called by point-motion hooks."
   (let ((msg (get-text-property new-point 'help-echo)))
     (when (and (stringp msg)
@@ -10568,9 +10550,9 @@ This ensures that the counts and `next-error' are 
correct."
                (not (current-message)))
       (message msg))))
 
-(defalias #'js2-echo-help #'js2-echo-error)
+(defalias 'js2-echo-help #'js2-echo-error)
 
-(defun js2-line-break (&optional soft)
+(defun js2-line-break (&optional _soft)
   "Break line at point and indent, continuing comment if within one.
 If inside a string, and `js2-concat-multiline-strings' is not
 nil, turn it into concatenation."
@@ -10591,14 +10573,10 @@ nil, turn it into concatenation."
 (defun js2-mode-split-string (parse-status)
   "Turn a newline in mid-string into a string concatenation.
 PARSE-STATUS is as documented in `parse-partial-sexp'."
-  (let* ((col (current-column))
-         (quote-char (nth 3 parse-status))
-         (string-beg (nth 8 parse-status))
+  (let* ((quote-char (nth 3 parse-status))
          (at-eol (eq js2-concat-multiline-strings 'eol)))
     (insert quote-char)
-    (if at-eol
-        (insert " +\n")
-      (insert "\n"))
+    (insert (if at-eol " +\n" "\n"))
     (unless at-eol
       (insert "+ "))
     (js2-indent-line)
@@ -10666,7 +10644,7 @@ PARSE-STATUS is as documented in `parse-partial-sexp'."
   "Toggle point between bol and first non-whitespace char in line.
 Also moves past comment delimiters when inside comments."
   (interactive)
-  (let (node beg)
+  (let (node)
     (cond
      ((bolp)
       (back-to-indentation))
@@ -10724,7 +10702,7 @@ Returns the created overlay if FLAG is non-nil."
 ;; Function to be set as an outline-isearch-open-invisible' property
 ;; to the overlay that makes the outline invisible (see
 ;; `js2-mode-flag-region').
-(defun js2-isearch-open-invisible (overlay)
+(defun js2-isearch-open-invisible (_overlay)
   ;; We rely on the fact that isearch places point on the matched text.
   (js2-mode-show-element))
 
@@ -10890,9 +10868,8 @@ to open an individual entry."
       (message "Oops - parsing failed")
     (setq js2-mode-comments-hidden t)
     (dolist (n (js2-ast-root-comments js2-mode-ast))
-      (let ((format (js2-comment-node-format n)))
-        (when (js2-block-comment-p n)
-          (js2-mode-hide-comment n))))
+      (when (js2-block-comment-p n)
+        (js2-mode-hide-comment n)))
     (js2-mode-hide-//-comments)))
 
 (defun js2-mode-extend-//-comment (direction)
@@ -11002,7 +10979,7 @@ move backward across N balanced expressions."
      ;;  - infix exprs like (foo in bar) - maybe go to beginning
      ;;    of infix expr if in the right-side expression?
      ((and arg (minusp arg))
-      (dotimes (i (- arg))
+      (dotimes (_ (- arg))
         (js2-backward-sws)
         (forward-char -1)   ; Enter the node we backed up to.
         (when (setq node (js2-node-at-point (point) t))
@@ -11026,7 +11003,7 @@ move backward across N balanced expressions."
         (unless pos (goto-char (point-min)))))
      (t
       ;; forward-sexp
-      (dotimes (i arg)
+      (dotimes (_ arg)
         (js2-forward-sws)
         (when (setq node (js2-node-at-point (point) t))
           (setq pos (js2-node-abs-pos node))
@@ -11075,7 +11052,7 @@ move backward across N balanced expressions."
     (catch 'done
       (js2-visit-ast
        parent
-       (lambda (node end-p)
+       (lambda (node _end-p)
          (if (eq node parent)
              t
            (let ((pos (js2-node-pos node)) ;; Both relative values.
@@ -11180,8 +11157,8 @@ destroying the region selection."
   (let (beg end)
     (js2-with-underscore-as-word-syntax
       (save-excursion
-        (if (and (not (looking-at "[A-Za-z0-9_$]"))
-                 (looking-back "[A-Za-z0-9_$]"))
+        (if (and (not (looking-at "[[:alnum:]_$]"))
+                 (looking-back "[[:alnum:]_$]"))
             (setq beg (progn (forward-word -1) (point))
                   end (progn (forward-word 1) (point)))
           (setq beg (progn (forward-word 1) (point))
@@ -11226,9 +11203,7 @@ With ARG N, do that N times. If N is negative, move 
forward."
               (js2-beginning-of-defun (1- arg))
             t)))
     (when (js2-end-of-defun)
-      (if (>= arg -1)
-          (js2-beginning-of-defun 1)
-        (js2-beginning-of-defun (1+ arg))))))
+      (js2-beginning-of-defun (if (>= arg -1) 1 (1+ arg))))))
 
 (defun js2-end-of-defun ()
   "Go to the char after the last position of the current function
@@ -11264,8 +11239,7 @@ it marks the next defun after the ones already marked."
       (let ((sib (save-excursion
                    (goto-char (mark))
                    (if (js2-mode-forward-sibling)
-                       (point))))
-            node)
+                       (point)))))
         (if sib
             (progn
               (set-mark sib)

commit 63f774d3765b6ad3af0e732ec7c31851d85895e0
Author: Dmitry Gutov <address@hidden>
Date:   Mon Sep 2 16:59:13 2013 +0300

    Fix (and rename) the ts-set-state test

diff --git a/tests/parser.el b/tests/parser.el
index 1842360..0a076e1 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -230,7 +230,7 @@ the test."
   (should (eq js2-NAME (js2-current-token-type)))
   (should (equal "x" (js2-current-token-string))))
 
-(js2-deftest ts-set-state "(1+2)"
+(js2-deftest ts-seek "(1+2)"
   (js2-init-scanner)
   (should (eq js2-LP (js2-next-token)))
   (should (eq js2-NUMBER (js2-next-token)))
@@ -238,7 +238,7 @@ the test."
   (let ((state (make-js2-ts-state)))
     (should (eq js2-NUMBER (js2-next-token)))
     (should (eq js2-ADD (js2-next-token)))
-    (js2-ts-set-state state)
+    (js2-ts-seek state)
     (should (eq 1 js2-ti-lookahead))
     (should (eq js2-NUMBER (js2-next-token)))
     (should (eq 1 (js2-token-number

commit 462b6a37705ec3100ea443eabc1f749662684f87
Author: Dmitry Gutov <address@hidden>
Date:   Mon Sep 2 16:51:27 2013 +0300

    Support arrow functions (#108)
    
    Roughly along the lines of
    https://hg.mozilla.org/mozilla-central/rev/bf3ce88c6ea3

diff --git a/js2-mode.el b/js2-mode.el
index 627e0d3..0174d32 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -644,10 +644,11 @@ which doesn't seem particularly useful, but Rhino permits 
it."
 (defvar js2-DEBUGGER 159)
 
 (defvar js2-COMMENT 160)
-(defvar js2-ENUM 161)  ; for "enum" reserved word
-(defvar js2-TRIPLEDOT 162) ; for rest parameter
+(defvar js2-ENUM 161)          ; for "enum" reserved word
+(defvar js2-TRIPLEDOT 162)     ; for rest parameter
+(defvar js2-ARROW 163)         ; function arrow (=>)
 
-(defconst js2-num-tokens (1+ js2-TRIPLEDOT))
+(defconst js2-num-tokens (1+ js2-ARROW))
 
 (defconst js2-debug-print-trees nil)
 
@@ -711,7 +712,7 @@ List of chars built up while scanning various tokens.")
 (defstruct (js2-ts-state
             (:constructor make-js2-ts-state (&key (lineno js2-ts-lineno)
                                                   (cursor js2-ts-cursor)
-                                                  (tokens js2-ti-tokens)
+                                                  (tokens (copy-sequence 
js2-ti-tokens))
                                                   (tokens-cursor 
js2-ti-tokens-cursor)
                                                   (lookahead 
js2-ti-lookahead))))
   lineno
@@ -1513,6 +1514,9 @@ the correct number of ARGS must be provided."
 (js2-msg "msg.param.after.rest" ; added by js2-mode
          "parameter after rest parameter")
 
+(js2-msg "msg.bad.arrow.args" ; added by js2-mode
+         "invalid arrow-function arguments (parentheses around the 
arrow-function may help)")
+
 (js2-msg "msg.no.brace.body"
          "missing '{' before function body")
 
@@ -1965,7 +1969,7 @@ the correct number of ARGS must be provided."
     (- (js2-token-end token)
        (js2-token-beg token))))
 
-(defun js2-ts-set-state (state)
+(defun js2-ts-seek (state)
   (setq js2-ts-lineno (js2-ts-state-lineno state)
         js2-ts-cursor (js2-ts-state-cursor state)
         js2-ti-tokens (js2-ts-state-tokens state)
@@ -2960,7 +2964,7 @@ The `params' field is a Lisp list of nodes.  Each node is 
either a simple
 `js2-name-node', or if it's a destructuring-assignment parameter, a
 `js2-array-node' or `js2-object-node'."
   ftype            ; FUNCTION, GETTER or SETTER
-  form             ; FUNCTION_{STATEMENT|EXPRESSION}
+  form             ; FUNCTION_{STATEMENT|EXPRESSION|ARROW}
   name             ; function name (a `js2-name-node', or nil if anonymous)
   params           ; a Lisp list of destructuring forms or simple name nodes
   rest-p           ; if t, the last parameter is rest parameter
@@ -2982,19 +2986,20 @@ The `params' field is a Lisp list of nodes.  Each node 
is either a simple
   (js2-visit-ast (js2-function-node-body n) v))
 
 (defun js2-print-function-node (n i)
-  (let ((pad (js2-make-pad i))
-        (getter (js2-node-get-prop n 'GETTER_SETTER))
-        (name (js2-function-node-name n))
-        (member-expr (js2-function-node-member-expr n))
-        (params (js2-function-node-params n))
-        (rest-p (js2-function-node-rest-p n))
-        (body (js2-function-node-body n))
-        (expr (eq (js2-function-node-form n) 'FUNCTION_EXPRESSION)))
-    (unless getter
+  (let* ((pad (js2-make-pad i))
+         (getter (js2-node-get-prop n 'GETTER_SETTER))
+         (name (or (js2-function-node-name n)
+                   (js2-function-node-member-expr n)))
+         (params (js2-function-node-params n))
+         (arrow (eq (js2-function-node-form n) 'FUNCTION_ARROW))
+         (rest-p (js2-function-node-rest-p n))
+         (body (js2-function-node-body n))
+         (expr (not (eq (js2-function-node-form n) 'FUNCTION_STATEMENT))))
+    (unless (or getter arrow)
       (insert pad "function"))
-    (when (or name member-expr)
+    (when name
       (insert " ")
-      (js2-print-ast (or name member-expr) 0))
+      (js2-print-ast name 0))
     (insert "(")
     (loop with len = (length params)
           for param in params
@@ -3005,11 +3010,16 @@ The `params' field is a Lisp list of nodes.  Each node 
is either a simple
           (js2-print-ast param 0)
           (when (< count len)
             (insert ", ")))
-    (insert ") {")
+    (insert ") ")
+    (when arrow
+      (insert "=> "))
+    (insert "{")
+    ;; TODO:  fix this to be smarter about indenting, etc.
     (unless expr
       (insert "\n"))
-    ;; TODO:  fix this to be smarter about indenting, etc.
-    (js2-print-body body (1+ i))
+    (if (js2-block-node-p body)
+        (js2-print-body body (1+ i))
+      (js2-print-ast body 0))
     (insert pad "}")
     (unless expr
       (insert "\n"))))
@@ -5714,7 +5724,9 @@ its relevant fields and puts it into `js2-ti-tokens'."
                (if (js2-match-char ?=)
                    (js2-ts-return token js2-SHEQ)
                  (throw 'return js2-EQ))
-             (throw 'return js2-ASSIGN)))
+             (if (js2-match-char ?>)
+                 (js2-ts-return token js2-ARROW)
+               (throw 'return js2-ASSIGN))))
           (?!
            (if (js2-match-char ?=)
                (if (js2-match-char ?=)
@@ -7290,15 +7302,20 @@ NODE is either `js2-array-node', `js2-object-node', or 
`js2-name-node'."
    (t (js2-report-error "msg.no.parm" nil (js2-node-abs-pos node)
                         (js2-node-len node)))))
 
-(defun js2-parse-function-params (fn-node pos)
+(defun js2-parse-function-params (function-type fn-node pos)
   (if (js2-match-token js2-RP)
       (setf (js2-function-node-rp fn-node) (- (js2-current-token-beg) pos))
-    (let (params len param default-found rest-param-at)
+    (let ((paren-free-arrow (and (eq function-type 'FUNCTION_ARROW)
+                                 (eq (js2-current-token-type) js2-NAME)))
+          params len param default-found rest-param-at)
+      (when paren-free-arrow
+        (js2-unget-token))
       (loop for tt = (js2-peek-token)
             do
             (cond
              ;; destructuring param
-             ((or (= tt js2-LB) (= tt js2-LC))
+             ((and (not paren-free-arrow)
+                   (or (= tt js2-LB) (= tt js2-LC)))
               (js2-get-token)
               (when default-found
                 (js2-report-error "msg.no.default.after.default.param"))
@@ -7310,6 +7327,7 @@ NODE is either `js2-array-node', `js2-object-node', or 
`js2-name-node'."
              ;; variable name
              (t
               (when (and (>= js2-language-version 200)
+                         (not paren-free-arrow)
                          (js2-match-token js2-TRIPLEDOT)
                          (not rest-param-at))
                 ;; to report errors if there are more parameters
@@ -7327,6 +7345,7 @@ NODE is either `js2-array-node', `js2-object-node', or 
`js2-name-node'."
                                              (js2-node-len param)))
                         (and (>= js2-language-version 200)
                              (js2-match-token js2-ASSIGN)))
+                (assert (not paren-free-arrow))
                 (let* ((pos (js2-node-pos param))
                        (tt (js2-current-token-type))
                        (op-pos (- (js2-current-token-beg) pos))
@@ -7344,7 +7363,8 @@ NODE is either `js2-array-node', `js2-object-node', or 
`js2-name-node'."
                                 (js2-node-pos param) (js2-node-len param)))
             while
             (js2-match-token js2-COMMA))
-      (when (js2-must-match js2-RP "msg.no.paren.after.parms")
+      (when (and (not paren-free-arrow)
+                 (js2-must-match js2-RP "msg.no.paren.after.parms"))
         (setf (js2-function-node-rp fn-node) (- (js2-current-token-beg) pos)))
       (when rest-param-at
         (setf (js2-function-node-rest-p fn-node) t))
@@ -7428,7 +7448,9 @@ arrow function), NAME is js2-name-node."
           js2-label-set
           js2-loop-set
           js2-loop-and-switch-set)
-      (js2-parse-function-params fn-node pos)
+      (js2-parse-function-params function-type fn-node pos)
+      (when (eq function-type 'FUNCTION_ARROW)
+        (js2-must-match js2-ARROW "msg.bad.arrow.args"))
       (if (and (>= js2-language-version 180)
                (/= (js2-peek-token) js2-LC))
           (js2-parse-function-closure-body fn-node)
@@ -8462,15 +8484,20 @@ If NODE is non-nil, it is the AST node associated with 
the symbol."
 (defun js2-parse-assign-expr ()
   (let ((tt (js2-get-token))
         (pos (js2-current-token-beg))
-        pn left right op-pos)
+        pn left right op-pos
+        ts-state recorded-identifiers)
     (if (= tt js2-YIELD)
         (js2-parse-return-or-yield tt t)
+      ;; Save the tokenizer state in case we find an arrow function
+      ;; and have to rewind.
+      (setq ts-state (make-js2-ts-state)
+            recorded-identifiers js2-recorded-identifiers)
       ;; not yield - parse assignment expression
       (setq pn (js2-parse-cond-expr)
             tt (js2-get-token))
-      (if (not (and (<= js2-first-assign tt)
-                    (<= tt js2-last-assign)))
-          (js2-unget-token)
+      (cond
+       ((and (<= js2-first-assign tt)
+             (<= tt js2-last-assign))
         ;; tt express assignment (=, |=, ^=, ..., %=)
         (setq op-pos (- (js2-current-token-beg) pos)  ; relative
               left pn)
@@ -8486,6 +8513,13 @@ If NODE is non-nil, it is the AST node associated with 
the symbol."
           (js2-record-imenu-functions right left))
         ;; do this last so ide checks above can use absolute positions
         (js2-node-add-children pn left right))
+       ((and (= tt js2-ARROW)
+             (>= js2-language-version 200))
+        (js2-ts-seek ts-state)
+        (setq js2-recorded-identifiers recorded-identifiers)
+        (setq pn (js2-parse-function 'FUNCTION_ARROW (js2-current-token-beg))))
+       (t
+        (js2-unget-token)))
       pn)))
 
 (defun js2-parse-cond-expr ()
@@ -9125,6 +9159,28 @@ array-literals, array comprehensions and regular 
expressions."
           (= tt js2-FALSE)
           (= tt js2-TRUE))
       (make-js2-keyword-node :type tt))
+     ((= tt js2-RP)
+      ;; Not valid expression syntax, but this is valid in an arrow
+      ;; function with no params: () => body.
+      (if (eq (js2-peek-token) js2-ARROW)
+          (progn
+            (js2-unget-token)  ; Put back the right paren.
+            ;; Return whatever, it will hopefully be rewinded and
+            ;; reparsed when we reach the =>.
+            (make-js2-keyword-node :type js2-NULL))
+        (js2-report-error "msg.syntax")
+        (make-js2-error-node)))
+     ((= tt js2-TRIPLEDOT)
+      ;; Likewise, only valid in an arrow function with a rest param.
+      (if (and (js2-match-token js2-NAME)
+               (js2-match-token js2-RP)
+               (eq (js2-peek-token) js2-ARROW))
+          (progn
+            (js2-unget-token)  ; Put back the right paren.
+            ;; See the previous case.
+            (make-js2-keyword-node :type js2-NULL))
+        (js2-report-error "msg.syntax")
+        (make-js2-error-node)))
      ((= tt js2-RESERVED)
       (js2-report-error "msg.reserved.id")
       (make-js2-name-node))
diff --git a/tests/parser.el b/tests/parser.el
index c2817b8..1842360 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -76,7 +76,7 @@ the test."
   "function foo() {\n}")
 
 (js2-deftest-parse function-expression-statements-are-verboten
-  "function() {\n}" :syntax-error "function")
+  "function() {}" :syntax-error "function")
 
 (js2-deftest-parse member-expr-as-function-name
   "function a.b.c[2](x, y) {\n}"
@@ -161,6 +161,20 @@ the test."
 (js2-deftest-parse function-with-rest-after-default-parameter
   "function foo(a = 1, ...rest) {\n}")
 
+;;; Arrow functions.
+
+(js2-deftest-parse arrow-function-with-empty-args-and-no-curlies
+  "() => false;" :reference "() => {false};")
+
+(js2-deftest-parse arrow-function-with-args-and-curlies
+  "(a, b = 1, ...c) => {  c;\n};")
+
+(js2-deftest-parse parenless-arrow-function-prohibits-rest
+  "...b => {b + 1;};" :syntax-error "b" :errors-count 2)
+
+(js2-deftest-parse parenless-arrow-function-prohibits-destructuring
+  "[a, b] => {a + b;};" :syntax-error "]" :errors-count 5)
+
 ;;; Automatic semicolon insertion.
 
 (js2-deftest-parse no-auto-semi-insertion-after-if

commit a93f25a3aa518d5c3c68d8ce42781ff1a4a6f3f3
Author: Dmitry Gutov <address@hidden>
Date:   Mon Sep 2 01:28:02 2013 +0300

    Extract js2-parse-function-stmt and js2-parse-function-expr
    
    * Fix printing of js2-function-nodes with member-expr.
    * Prohibit unnamed function expressions where statement is expected.
    * Omit the case of "random-member-expr", whatever that is.
      Anyone who misses it is welcome to file an issue.

diff --git a/js2-mode.el b/js2-mode.el
index abc4dc2..627e0d3 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -1495,6 +1495,9 @@ the correct number of ARGS must be provided."
          "Line terminator is not allowed between the throw "
          "keyword and throw expression.")
 
+(js2-msg "msg.unnamed.function.stmt" ; added by js2-mode
+         "function statement requires a name")
+
 (js2-msg "msg.no.paren.parms"
          "missing ( before function parameters.")
 
@@ -2957,7 +2960,7 @@ The `params' field is a Lisp list of nodes.  Each node is 
either a simple
 `js2-name-node', or if it's a destructuring-assignment parameter, a
 `js2-array-node' or `js2-object-node'."
   ftype            ; FUNCTION, GETTER or SETTER
-  form             ; FUNCTION_{STATEMENT|EXPRESSION|EXPRESSION_STATEMENT}
+  form             ; FUNCTION_{STATEMENT|EXPRESSION}
   name             ; function name (a `js2-name-node', or nil if anonymous)
   params           ; a Lisp list of destructuring forms or simple name nodes
   rest-p           ; if t, the last parameter is rest parameter
@@ -2982,15 +2985,16 @@ The `params' field is a Lisp list of nodes.  Each node 
is either a simple
   (let ((pad (js2-make-pad i))
         (getter (js2-node-get-prop n 'GETTER_SETTER))
         (name (js2-function-node-name n))
+        (member-expr (js2-function-node-member-expr n))
         (params (js2-function-node-params n))
         (rest-p (js2-function-node-rest-p n))
         (body (js2-function-node-body n))
         (expr (eq (js2-function-node-form n) 'FUNCTION_EXPRESSION)))
     (unless getter
       (insert pad "function"))
-    (when name
-        (insert " ")
-        (js2-print-ast name 0))
+    (when (or name member-expr)
+      (insert " ")
+      (js2-print-ast (or name member-expr) 0))
     (insert "(")
     (loop with len = (length params)
           for param in params
@@ -7192,9 +7196,9 @@ Scanner should be initialized."
     (while (/= (setq tt (js2-get-token)) js2-EOF)
       (if (= tt js2-FUNCTION)
           (progn
-            (setq n (js2-parse-function (if js2-called-by-compile-function
-                                            'FUNCTION_EXPRESSION
-                                          'FUNCTION_STATEMENT))))
+            (setq n (if js2-called-by-compile-function
+                        (js2-parse-function-expr)
+                      (js2-parse-function-stmt))))
         ;; not a function - parse a statement
         (js2-unget-token)
         (setq n (js2-parse-statement)))
@@ -7219,7 +7223,7 @@ Scanner should be initialized."
 
 (defun js2-function-parser ()
   (js2-get-token)
-  (js2-parse-function 'FUNCTION_EXPRESSION_STATEMENT))
+  (js2-parse-function-stmt))
 
 (defun js2-parse-function-closure-body (fn-node)
   "Parse a JavaScript 1.8 function closure body."
@@ -7248,7 +7252,7 @@ Scanner should be initialized."
           (js2-block-node-push pn (if (/= tt js2-FUNCTION)
                                       (js2-parse-statement)
                                     (js2-get-token)
-                                    (js2-parse-function 'FUNCTION_STATEMENT))))
+                                    (js2-parse-function-stmt))))
       (decf js2-nesting-of-function))
     (setq end (js2-current-token-end))  ; assume no curly and leave at current 
token
     (if (js2-must-match js2-RC "msg.no.brace.after.body" pos)
@@ -7366,50 +7370,45 @@ Last token scanned is the close-curly for the function 
body."
                                   (js2-name-node-name name) pos end)
         (js2-add-strict-warning "msg.anon.no.return.value" nil pos end)))))
 
-(defun js2-parse-function (function-type)
-  "Function parser.  FUNCTION-TYPE is a symbol."
-  (let ((pos (js2-current-token-beg))  ; start of 'function' keyword
-        name name-beg name-end fn-node lp
-        (synthetic-type function-type)
-        member-expr-node)
-    ;; parse function name, expression, or non-name (anonymous)
-    (cond
-     ;; function foo(...)
-     ((js2-match-token js2-NAME)
-      (setq name (js2-create-name-node t)
-            name-beg (js2-current-token-beg)
-            name-end (js2-current-token-end))
-      (unless (js2-match-token js2-LP)
-        (when js2-allow-member-expr-as-function-name
-          ;; function foo.bar(...)
-          (setq member-expr-node name
-                name nil
-                member-expr-node (js2-parse-member-expr-tail
-                                  nil member-expr-node)))
-        (js2-must-match js2-LP "msg.no.paren.parms")))
-     ((js2-match-token js2-LP)
-      nil)  ; anonymous function:  leave name as null
-     (t
-      ;; function random-member-expr(...)
-      (when js2-allow-member-expr-as-function-name
-        ;; Note that memberExpr can not start with '(' like
-        ;; in function (1+2).toString(), because 'function (' already
-        ;; processed as anonymous function
-        (setq member-expr-node (js2-parse-member-expr)))
-      (js2-must-match js2-LP "msg.no.paren.parms")))
+(defun js2-parse-function-stmt ()
+  (let ((pos (js2-current-token-beg)))
+    (js2-must-match js2-NAME "msg.unnamed.function.stmt")
+    (let ((name (js2-create-name-node t))
+          pn member-expr)
+      (cond
+       ((js2-match-token js2-LP)
+        (js2-parse-function 'FUNCTION_STATEMENT pos name))
+       (js2-allow-member-expr-as-function-name
+        (setq member-expr (js2-parse-member-expr-tail nil name))
+        (js2-parse-highlight-member-expr-fn-name member-expr)
+        (js2-must-match js2-LP "msg.no.paren.parms")
+        (setf pn (js2-parse-function 'FUNCTION_STATEMENT pos)
+              (js2-function-node-member-expr pn) member-expr)
+        pn)
+       (t
+        (js2-report-error "msg.no.paren.parms"))))))
+
+(defun js2-parse-function-expr ()
+  (let ((pos (js2-current-token-beg))
+        name)
+    (when (js2-match-token js2-NAME)
+      (setq name (js2-create-name-node t)))
+    (js2-must-match js2-LP "msg.no.paren.parms")
+    (js2-parse-function 'FUNCTION_EXPRESSION pos name)))
+
+(defun js2-parse-function (function-type pos &optional name)
+  "Function parser.  FUNCTION-TYPE is a symbol, POS is the
+beginning of the first token (function keyword, unless it's an
+arrow function), NAME is js2-name-node."
+  (let (fn-node lp)
     (if (= (js2-current-token-type) js2-LP) ; eventually matched LP?
         (setq lp (js2-current-token-beg)))
-    (if member-expr-node
-        (progn
-          (setq synthetic-type 'FUNCTION_EXPRESSION)
-          (js2-parse-highlight-member-expr-fn-name member-expr-node))
-      (if name
-          (js2-set-face name-beg name-end
-                        'font-lock-function-name-face 'record)))
-    (if (and (not (eq synthetic-type 'FUNCTION_EXPRESSION))
-             (plusp (js2-name-node-length name)))
+    (when name
+      (js2-set-face (js2-node-pos name) (js2-node-end name)
+                    'font-lock-function-name-face 'record)
+      (when (plusp (js2-name-node-length name))
         ;; Function statements define a symbol in the enclosing scope
-        (js2-define-symbol js2-FUNCTION (js2-name-node-name name) fn-node))
+        (js2-define-symbol js2-FUNCTION (js2-name-node-name name) fn-node)))
     (setf fn-node (make-js2-function-node :pos pos
                                           :name name
                                           :form function-type
@@ -7434,23 +7433,22 @@ Last token scanned is the close-curly for the function 
body."
                (/= (js2-peek-token) js2-LC))
           (js2-parse-function-closure-body fn-node)
         (js2-parse-function-body fn-node))
-      (if name
-          (js2-node-add-children fn-node name))
       (js2-check-inconsistent-return-warning fn-node name)
-      ;; Function expressions define a name only in the body of the
-      ;; function, and only if not hidden by a parameter name
-      (if (and name
-               (eq synthetic-type 'FUNCTION_EXPRESSION)
-               (null (js2-scope-get-symbol js2-current-scope
-                                           (js2-name-node-name name))))
+
+      (when name
+        (js2-node-add-children fn-node name)
+        ;; Function expressions define a name only in the body of the
+        ;; function, and only if not hidden by a parameter name
+        (when (and (eq function-type 'FUNCTION_EXPRESSION)
+                   (null (js2-scope-get-symbol js2-current-scope
+                                               (js2-name-node-name name))))
           (js2-define-symbol js2-FUNCTION
                              (js2-name-node-name name)
                              fn-node))
-      (if (and name
-               (not (eq function-type 'FUNCTION_EXPRESSION)))
-          (js2-record-imenu-functions fn-node)))
-    (setf (js2-node-len fn-node) (- js2-ts-cursor pos)
-          (js2-function-node-member-expr fn-node) member-expr-node)  ; may be 
nil
+        (when (eq function-type 'FUNCTION_STATEMENT)
+          (js2-record-imenu-functions fn-node))))
+
+    (setf (js2-node-len fn-node) (- js2-ts-cursor pos))
     ;; Rhino doesn't do this, but we need it for finding undeclared vars.
     ;; We wait until after parsing the function to set its parent scope,
     ;; since `js2-define-symbol' needs the defining-scope check to stop
@@ -9084,7 +9082,7 @@ array-literals, array comprehensions and regular 
expressions."
     (setq tt (js2-current-token-type))
     (cond
      ((= tt js2-FUNCTION)
-      (js2-parse-function 'FUNCTION_EXPRESSION))
+      (js2-parse-function-expr))
      ((= tt js2-LB)
       (js2-parse-array-literal))
      ((= tt js2-LC)
@@ -9446,7 +9444,7 @@ PROP is the `js2-name-node' representing the property 
name.
 GET-P is non-nil if the keyword was `get'."
   (let ((type (if get-p js2-GET js2-SET))
         result end
-        (fn (js2-parse-function 'FUNCTION_EXPRESSION)))
+        (fn (js2-parse-function-expr)))
     ;; it has to be an anonymous function, as we already parsed the name
     (if (/= (js2-node-type fn) js2-FUNCTION)
         (js2-report-error "msg.bad.prop")
diff --git a/tests/parser.el b/tests/parser.el
index 0593d34..c2817b8 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -70,7 +70,20 @@ the test."
   "d = /eee/, 42;")
 
 (js2-deftest-parse return-statement
-  "function() {\n  return 2;\n}")
+  "function foo() {\n  return 2;\n}")
+
+(js2-deftest-parse function-statement
+  "function foo() {\n}")
+
+(js2-deftest-parse function-expression-statements-are-verboten
+  "function() {\n}" :syntax-error "function")
+
+(js2-deftest-parse member-expr-as-function-name
+  "function a.b.c[2](x, y) {\n}"
+  :bind ((js2-allow-member-expr-as-function-name t)))
+
+(js2-deftest-parse named-function-expression
+  "a = function b() {};")
 
 ;;; Callers of `js2-valid-prop-name-token'.
 

commit 59fea24dd6961e835726283f26e4d162fb452143
Author: Dmitry Gutov <address@hidden>
Date:   Sun Sep 1 07:20:10 2013 +0300

    Implement "save & restore" for token stream

diff --git a/js2-mode.el b/js2-mode.el
index 9217b74..abc4dc2 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -708,6 +708,18 @@ List of chars built up while scanning various tokens.")
   comment-type
   follows-eol-p)
 
+(defstruct (js2-ts-state
+            (:constructor make-js2-ts-state (&key (lineno js2-ts-lineno)
+                                                  (cursor js2-ts-cursor)
+                                                  (tokens js2-ti-tokens)
+                                                  (tokens-cursor 
js2-ti-tokens-cursor)
+                                                  (lookahead 
js2-ti-lookahead))))
+  lineno
+  cursor
+  tokens
+  tokens-cursor
+  lookahead)
+
 ;;; Parser variables
 
 (js2-deflocal js2-parsed-errors nil
@@ -1950,6 +1962,13 @@ the correct number of ARGS must be provided."
     (- (js2-token-end token)
        (js2-token-beg token))))
 
+(defun js2-ts-set-state (state)
+  (setq js2-ts-lineno (js2-ts-state-lineno state)
+        js2-ts-cursor (js2-ts-state-cursor state)
+        js2-ti-tokens (js2-ts-state-tokens state)
+        js2-ti-tokens-cursor (js2-ts-state-tokens-cursor state)
+        js2-ti-lookahead (js2-ts-state-lookahead state)))
+
 ;;; Utilities
 
 (defun js2-delete-if (predicate list)
diff --git a/tests/parser.el b/tests/parser.el
index d7386e8..0593d34 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -202,3 +202,17 @@ the test."
   (js2-unget-token)
   (should (eq js2-NAME (js2-current-token-type)))
   (should (equal "x" (js2-current-token-string))))
+
+(js2-deftest ts-set-state "(1+2)"
+  (js2-init-scanner)
+  (should (eq js2-LP (js2-next-token)))
+  (should (eq js2-NUMBER (js2-next-token)))
+  (js2-unget-token)
+  (let ((state (make-js2-ts-state)))
+    (should (eq js2-NUMBER (js2-next-token)))
+    (should (eq js2-ADD (js2-next-token)))
+    (js2-ts-set-state state)
+    (should (eq 1 js2-ti-lookahead))
+    (should (eq js2-NUMBER (js2-next-token)))
+    (should (eq 1 (js2-token-number
+                   (js2-current-token))))))

commit 9954c60292ea5d7a5a7725d208ed2a94a0051e14
Author: Dmitry Gutov <address@hidden>
Date:   Sun Sep 1 06:33:24 2013 +0300

    * js2-get-string-from-buffer: Rename, make it take token argument
    
    * js2-collect-string: Simplify
    
    * Having the global `js2-ts-string-buffer' var is still necessary,
      until `js2-add-to-string' is replaced with simple `push'.

diff --git a/js2-mode.el b/js2-mode.el
index f4aa294..9217b74 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -5041,12 +5041,10 @@ Signals an error if it's not a recognized token."
   (unless no-throw
     (throw 'return js2-ERROR)))
 
-(defun js2-get-string-from-buffer ()
-  "Reverse the char accumulator and return it as a string."
-  (setf (js2-token-end (js2-current-token)) js2-ts-cursor)
-  (if js2-ts-string-buffer
-      (apply #'string (nreverse js2-ts-string-buffer))
-    ""))
+(defun js2-set-string-from-buffer (token)
+  "Set `string' and `end' slots for TOKEN, return the string."
+  (setf (js2-token-end token) js2-ts-cursor
+        (js2-token-string token) (js2-collect-string js2-ts-string-buffer)))
 
 ;; TODO:  could potentially avoid a lot of consing by allocating a
 ;; char buffer the way Rhino does.
@@ -5279,15 +5277,9 @@ The values are default faces to use for highlighting the 
keywords.")
 (defun js2-collect-string (buf)
   "Convert BUF, a list of chars, to a string.
 Reverses BUF before converting."
-  (cond
-   ((stringp buf)
-    buf)
-   ((null buf)  ; for emacs21 compat
-    "")
-   (t
-    (if buf
-        (apply #'string (nreverse buf))
-      ""))))
+  (if buf
+      (apply #'string (nreverse buf))
+    ""))
 
 (defun js2-string-to-keyword (s)
   "Return token for S, a string, if S is a keyword or reserved word.
@@ -5472,7 +5464,8 @@ its relevant fields and puts it into `js2-ti-tokens'."
                       (throw 'break nil))
                   (js2-add-to-string c))))))
           (js2-unget-char)
-          (setq str (js2-get-string-from-buffer))
+          (setf str (js2-collect-string js2-ts-string-buffer)
+                (js2-token-end token) js2-ts-cursor)
           (unless contains-escape
             ;; OPT we shouldn't have to make a string (object!) to
             ;; check if it's a keyword.
@@ -5539,9 +5532,8 @@ its relevant fields and puts it into `js2-ti-tokens'."
                     (setq c (js2-get-char))
                     while (js2-digit-p c))))
           (js2-unget-char)
-          (let ((str (js2-get-string-from-buffer)))
-            (setf (js2-token-string token) str
-                  (js2-token-number token)
+          (let ((str (js2-set-string-from-buffer token)))
+            (setf (js2-token-number token)
                   (if (and (eq base 10) (not is-integer))
                       (string-to-number str)
                     ;; TODO:  call runtime number-parser.  Some of it is in
@@ -5645,7 +5637,7 @@ its relevant fields and puts it into `js2-ti-tokens'."
                        (setq c val)))))
                 (js2-add-to-string c)
                 (setq c (js2-get-char)))))
-          (setf (js2-token-string token) (js2-get-string-from-buffer))
+          (js2-set-string-from-buffer token)
           (throw 'return js2-STRING))
         (case c
           (?\;
@@ -5862,8 +5854,7 @@ its relevant fields and puts it into `js2-ti-tokens'."
       (if (js2-alpha-p (js2-peek-char))
           (js2-report-scan-error "msg.invalid.re.flag" t
                                  js2-ts-cursor 1))
-      (setf (js2-token-string token) (js2-collect-string js2-ts-string-buffer)
-            (js2-token-end token) js2-ts-cursor)
+      (js2-set-string-from-buffer token)
       ;; tell `parse-partial-sexp' to ignore this range of chars
       (js2-record-text-property (js2-current-token-beg)
                                 (js2-current-token-end) 'syntax-class '(2)))
@@ -5908,7 +5899,7 @@ its relevant fields and puts it into `js2-ti-tokens'."
                      (decf js2-ts-xml-open-tags-count)))
                   (?{
                    (js2-unget-char)
-                   (setf (js2-token-string token) (js2-get-string-from-buffer))
+                   (js2-set-string-from-buffer token)
                    (throw 'return js2-XML))
                   ((?\' ?\")
                    (js2-add-to-string c)
@@ -5924,7 +5915,7 @@ its relevant fields and puts it into `js2-ti-tokens'."
                    (setq js2-ts-is-xml-attribute nil)))
                 (when (and (not js2-ts-xml-is-tag-content)
                            (zerop js2-ts-xml-open-tags-count))
-                  (setf (js2-token-string token) (js2-get-string-from-buffer))
+                  (js2-set-string-from-buffer token)
                   (throw 'return js2-XMLEND)))
                (t
                 ;; else not tag content
@@ -5995,7 +5986,7 @@ its relevant fields and puts it into `js2-ti-tokens'."
                       (incf js2-ts-xml-open-tags-count))))
                   (?{
                    (js2-unget-char)
-                   (setf (js2-token-string token) (js2-get-string-from-buffer))
+                   (js2-set-string-from-buffer token)
                    (throw 'return js2-XML))
                   (t
                    (js2-add-to-string c))))))))

commit 0dad204bd0eda8ec2b16496ba1fd5daf60c25318
Author: Dmitry Gutov <address@hidden>
Date:   Sun Sep 1 05:21:45 2013 +0300

    js2-record-face: Accept optional, second parameter
    
    No significant improvement in performance.

diff --git a/js2-mode.el b/js2-mode.el
index a239119..f4aa294 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -5357,32 +5357,34 @@ the token is flagged as such."
   (setq js2-ti-tokens-cursor (mod (1- js2-ti-tokens-cursor) js2-ti-ntokens)))
 
 (defun js2-get-token-internal ()
-  (let ((tt (js2-get-token-internal-1))      ; call scanner
-        saw-eol
-        face)
+  (let* ((token (js2-get-token-internal-1)) ; call scanner
+         (tt (js2-token-type token))
+         saw-eol
+         face)
     ;; process comments
     (while (or (= tt js2-EOL) (= tt js2-COMMENT))
       (if (= tt js2-EOL)
           (setq saw-eol t)
         (setq saw-eol nil)
-        (if js2-record-comments
-          (js2-record-comment)))
+        (when js2-record-comments
+          (js2-record-comment token)))
       (setq js2-ti-tokens-cursor (mod (1- js2-ti-tokens-cursor) 
js2-ti-ntokens))
-      (setq tt (js2-get-token-internal-1)))  ; call scanner again
+      (setq token (js2-get-token-internal-1) ; call scanner again
+            tt (js2-token-type token)))
 
     (when saw-eol
-      (setf (js2-token-follows-eol-p (js2-current-token)) t))
+      (setf (js2-token-follows-eol-p token) t))
 
     ;; perform lexical fontification as soon as token is scanned
     (when js2-parse-ide-mode
       (cond
        ((minusp tt)
-        (js2-record-face 'js2-error))
+        (js2-record-face 'js2-error token))
        ((setq face (aref js2-kwd-tokens tt))
-        (js2-record-face face))
+        (js2-record-face face token))
        ((and (= tt js2-NAME)
-             (equal (js2-current-token-string) "undefined"))
-        (js2-record-face 'font-lock-constant-face))))
+             (equal (js2-token-string token) "undefined"))
+        (js2-record-face 'font-lock-constant-face token))))
     tt))
 
 (defun js2-get-token-internal-1 ()
@@ -5812,7 +5814,7 @@ its relevant fields and puts it into `js2-ti-tokens'."
           (otherwise
            (js2-report-scan-error "msg.illegal.character"))))))
    (setf (js2-token-type token) tt)
-   tt))
+   token))
 
 (defun js2-read-regexp (start-tt)
   "Called by parser when it gets / or /= in literal context."
@@ -6944,9 +6946,10 @@ i.e. one or more nodes, and an integer position as the 
list tail."
 (defconst js2-version "1.8.5"
   "Version of JavaScript supported.")
 
-(defmacro js2-record-face (face)
-  "Record a style run of FACE for the current token."
-  `(js2-set-face (js2-current-token-beg) (js2-current-token-end) ,face 
'record))
+(defun js2-record-face (face &optional token)
+  "Record a style run of FACE for TOKEN or the current token."
+  (unless token (setq token (js2-current-token)))
+  (js2-set-face (js2-token-beg token) (js2-token-end token) face 'record))
 
 (defsubst js2-node-end (n)
   "Computes the absolute end of node N.
@@ -6955,23 +6958,22 @@ is only true until the node is added to its parent; 
i.e., while parsing."
   (+ (js2-node-pos n)
      (js2-node-len n)))
 
-(defun js2-record-comment ()
+(defun js2-record-comment (token)
   "Record a comment in `js2-scanned-comments'."
-  (push (make-js2-comment-node :len (js2-current-token-len)
-                               :format (js2-token-comment-type
-                                        (js2-current-token)))
-        js2-scanned-comments)
-  (when js2-parse-ide-mode
-    (js2-record-face (if (eq (js2-token-comment-type
-                              (js2-current-token))
-                             'jsdoc)
-                         'font-lock-doc-face
-                       'font-lock-comment-face))
-    (when (memq (js2-token-comment-type
-                 (js2-current-token))
-                '(html preprocessor))
-      ;; Tell cc-engine the bounds of the comment.
-      (js2-record-text-property (js2-current-token-beg) (1- 
(js2-current-token-end)) 'c-in-sws t))))
+  (let ((ct (js2-token-comment-type token))
+        (beg (js2-token-beg token))
+        (end (js2-token-end token)))
+    (push (make-js2-comment-node :len (- end beg)
+                                 :format ct)
+          js2-scanned-comments)
+    (when js2-parse-ide-mode
+      (js2-record-face (if (eq ct 'jsdoc)
+                           'font-lock-doc-face
+                         'font-lock-comment-face)
+                       token)
+      (when (memq ct '(html preprocessor))
+        ;; Tell cc-engine the bounds of the comment.
+        (js2-record-text-property beg (1- end) 'c-in-sws t)))))
 
 (defun js2-peek-token ()
   "Return the next token type without consuming it.

commit 72d28d82bc1d742a33cf5afa8180707b375eccbf
Author: Dmitry Gutov <address@hidden>
Date:   Fri Aug 30 01:58:33 2013 +0300

    Implement two-token lookahead
    
    * Use `js2-token' defstruct to store token info.
    * Cache tokens in a vector buffer `js2-ti-tokens'.
    * Obviously, a deoptimization, gives us performance hit:
      - ~60% slower in interpreted mode (Emacs 24.3.50.1).
      - ~50% slower in compiled mode.
    * `mod' is actually slightly faster than `logand' for our usage.
    * Optimizations tried:
      - Cache the current token.
      - Cache the current token type.
      - Reuse the token structs, instead of creating new one each time.
      - Set the js2-token struct's type to `vector' or `list', disabling
        the usage of the tag symbol, and thus getting rid of type checking.
        Gives us ~15% improvement in interpreted mode, but no significant
        change in compiled mode.

diff --git a/js2-mode.el b/js2-mode.el
index 6842056..a239119 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -455,10 +455,6 @@ which doesn't seem particularly useful, but Rhino permits 
it."
      (defvar ,name ,value ,comment)
      (make-variable-buffer-local ',name)))
 
-;; We record the start and end position of each token.
-(js2-deflocal js2-token-beg 1)
-(js2-deflocal js2-token-end -1)
-
 (defvar js2-EOF_CHAR -1
   "Represents end of stream.  Distinct from js2-EOF token type.")
 
@@ -667,26 +663,17 @@ which doesn't seem particularly useful, but Rhino permits 
it."
   "Token stream buffer-local variable.
 Indicates stuff other than whitespace since start of line.")
 
-(js2-deflocal js2-ts-regexp-flags nil
-  "Token stream buffer-local variable.")
-
-(js2-deflocal js2-ts-string ""
-  "Token stream buffer-local variable.
-Last string scanned.")
-
-(js2-deflocal js2-ts-number nil
-  "Token stream buffer-local variable.
-Last literal number scanned.")
-
 (js2-deflocal js2-ts-hit-eof nil
   "Token stream buffer-local variable.")
 
+;; FIXME: Unused.
 (js2-deflocal js2-ts-line-start 0
   "Token stream buffer-local variable.")
 
 (js2-deflocal js2-ts-lineno 1
   "Token stream buffer-local variable.")
 
+;; FIXME: Unused.
 (js2-deflocal js2-ts-line-end-char -1
   "Token stream buffer-local variable.")
 
@@ -694,6 +681,7 @@ Last literal number scanned.")
   "Token stream buffer-local variable.
 Current scan position.")
 
+;; FIXME: Unused.
 (js2-deflocal js2-ts-is-xml-attribute nil
   "Token stream buffer-local variable.")
 
@@ -707,8 +695,18 @@ Current scan position.")
   "Token stream buffer-local variable.
 List of chars built up while scanning various tokens.")
 
-(js2-deflocal js2-ts-comment-type nil
-  "Token stream buffer-local variable.")
+(defstruct (js2-token
+            (:constructor nil)
+            (:constructor make-js2-token (beg)))
+  "Value returned from the token stream."
+  (type js2-EOF)
+  (beg 1)
+  (end -1)
+  (string "")
+  number
+  regexp-flags
+  comment-type
+  follows-eol-p)
 
 ;;; Parser variables
 
@@ -750,15 +748,9 @@ parser as a frontend to an interpreter or byte compiler.")
 
 ;;; Parser instance variables (buffer-local vars for js2-parse)
 
-(defconst js2-clear-ti-mask #xFFFF
-  "Mask to clear token information bits.")
-
 (defconst js2-ti-after-eol (lsh 1 16)
   "Flag:  first token of the source line.")
 
-(defconst js2-ti-check-label (lsh 1 17)
-  "Flag:  indicates to check for label.")
-
 ;; Inline Rhino's CompilerEnvirons vars as buffer-locals.
 
 (js2-deflocal js2-compiler-generate-debug-info t)
@@ -781,9 +773,6 @@ Will only be used when we finish implementing the 
interpreter.")
 
 ;; SKIP:  ts  (we just call `js2-init-scanner' and use its vars)
 
-(js2-deflocal js2-current-flagged-token js2-EOF)
-(js2-deflocal js2-current-token js2-EOF)
-
 ;; SKIP:  node factory - we're going to just call functions directly,
 ;; and eventually go to a unified AST format.
 
@@ -892,16 +881,6 @@ buffer text for your imports, using regular expressions.")
 
 ;;; ...end of per function variables
 
-;; Without 2-token lookahead, labels are a problem.
-;; These vars store the token info of the last matched name,
-;; iff it wasn't the last matched token.  Only valid in some contexts.
-(defvar js2-prev-name-token-start nil)
-(defvar js2-prev-name-token-string nil)
-
-(defsubst js2-save-name-token-data (pos name)
-  (setq js2-prev-name-token-start pos
-        js2-prev-name-token-string name))
-
 ;; These flags enumerate the possible ways a statement/function can
 ;; terminate. These flags are used by endCheck() and by the Parser to
 ;; detect inconsistent return usage.
@@ -1935,6 +1914,42 @@ the correct number of ARGS must be provided."
 (js2-msg "msg.yield.closing"
          "Yield from closing generator")
 
+;;; Tokens Buffer
+
+(defconst js2-ti-max-lookahead 2)
+(defconst js2-ti-ntokens (1+ js2-ti-max-lookahead))
+
+;; Have to call `js2-init-scanner' to initialize the values.
+(js2-deflocal js2-ti-tokens nil)
+(js2-deflocal js2-ti-tokens-cursor nil)
+(js2-deflocal js2-ti-lookahead nil)
+
+(defun js2-new-token (offset)
+  (let ((token (make-js2-token (+ offset js2-ts-cursor))))
+    (setq js2-ti-tokens-cursor (mod (1+ js2-ti-tokens-cursor) js2-ti-ntokens))
+    (aset js2-ti-tokens js2-ti-tokens-cursor token)
+    token))
+
+(defsubst js2-current-token ()
+  (aref js2-ti-tokens js2-ti-tokens-cursor))
+
+(defsubst js2-current-token-string ()
+  (js2-token-string (js2-current-token)))
+
+(defsubst js2-current-token-type ()
+  (js2-token-type (js2-current-token)))
+
+(defsubst js2-current-token-beg ()
+  (js2-token-beg (js2-current-token)))
+
+(defsubst js2-current-token-end ()
+  (js2-token-end (js2-current-token)))
+
+(defun js2-current-token-len ()
+  (let ((token (js2-current-token)))
+    (- (js2-token-end token)
+       (js2-token-beg token))))
+
 ;;; Utilities
 
 (defun js2-delete-if (predicate list)
@@ -1994,8 +2009,8 @@ Returns nil if element is not found in the list."
 ;; easiest thing to do is get the context info from the last token.
 (defun js2-record-parse-error (msg &optional arg pos len)
   (push (list (list msg arg)
-              (or pos js2-token-beg)
-              (or len (- js2-token-end js2-token-beg)))
+              (or pos (js2-current-token-beg))
+              (or len (js2-current-token-len)))
         js2-parsed-errors))
 
 (defun js2-report-error (msg &optional msg-arg pos len)
@@ -2014,8 +2029,8 @@ Returns nil if element is not found in the list."
   (if js2-compiler-report-warning-as-error
       (js2-report-error msg msg-arg pos len)
     (push (list (list msg msg-arg)
-                (or pos js2-token-beg)
-                (or len (- js2-token-end js2-token-beg))
+                (or pos (js2-current-token-beg))
+                (or len (js2-current-token-len))
                 face)
           js2-parsed-warnings)))
 
@@ -2168,7 +2183,7 @@ If any given node in NODES is nil, doesn't record that 
link."
             (:include js2-node)
             (:constructor nil)
             (:constructor make-js2-block-node (&key (type js2-BLOCK)
-                                                    (pos js2-token-beg)
+                                                    (pos 
(js2-current-token-beg))
                                                     len
                                                     props
                                                     kids)))
@@ -2194,7 +2209,7 @@ If any given node in NODES is nil, doesn't record that 
link."
             (:include js2-block-node)
             (:constructor nil)
             (:constructor make-js2-scope (&key (type js2-BLOCK)
-                                               (pos js2-token-beg)
+                                               (pos (js2-current-token-beg))
                                                len
                                                kids)))
   ;; The symbol-table is a LinkedHashMap<String,Symbol> in Rhino.
@@ -2269,7 +2284,7 @@ NAME can be a Lisp symbol or string.  SYMBOL is a 
`js2-symbol'."
             (:include js2-node)
             (:constructor nil) ; silence emacs21 byte-compiler
             (:constructor make-js2-error-node (&key (type js2-ERROR)
-                                                    (pos js2-token-beg)
+                                                    (pos 
(js2-current-token-beg))
                                                     len)))
   "AST node representing a parse error.")
 
@@ -2280,7 +2295,7 @@ NAME can be a Lisp symbol or string.  SYMBOL is a 
`js2-symbol'."
             (:include js2-scope)
             (:constructor nil)
             (:constructor make-js2-script-node (&key (type js2-SCRIPT)
-                                                     (pos js2-token-beg)
+                                                     (pos 
(js2-current-token-beg))
                                                      len
                                                      var-decls
                                                      fun-decls)))
@@ -2303,7 +2318,7 @@ NAME can be a Lisp symbol or string.  SYMBOL is a 
`js2-symbol'."
             (:include js2-script-node)
             (:constructor nil)
             (:constructor make-js2-ast-root (&key (type js2-SCRIPT)
-                                                  (pos js2-token-beg)
+                                                  (pos (js2-current-token-beg))
                                                   len
                                                   buffer)))
   "The root node of a js2 AST."
@@ -2326,9 +2341,9 @@ NAME can be a Lisp symbol or string.  SYMBOL is a 
`js2-symbol'."
             (:include js2-node)
             (:constructor nil)
             (:constructor make-js2-comment-node (&key (type js2-COMMENT)
-                                                      (pos js2-token-beg)
+                                                      (pos 
(js2-current-token-beg))
                                                       len
-                                                      (format 
js2-ts-comment-type))))
+                                                      format)))
   format)  ; 'line, 'block, 'jsdoc or 'html
 
 (put 'cl-struct-js2-comment-node 'js2-visitor 'js2-visit-none)
@@ -2376,7 +2391,7 @@ NAME can be a Lisp symbol or string.  SYMBOL is a 
`js2-symbol'."
             (:include js2-loop-node)
             (:constructor nil)
             (:constructor make-js2-do-node (&key (type js2-DO)
-                                                 (pos js2-token-beg)
+                                                 (pos (js2-current-token-beg))
                                                  len
                                                  body
                                                  condition
@@ -2407,7 +2422,7 @@ NAME can be a Lisp symbol or string.  SYMBOL is a 
`js2-symbol'."
             (:include js2-loop-node)
             (:constructor nil)
             (:constructor make-js2-while-node (&key (type js2-WHILE)
-                                                    (pos js2-token-beg)
+                                                    (pos 
(js2-current-token-beg))
                                                     len body
                                                     condition lp
                                                     rp)))
@@ -2990,7 +3005,7 @@ The `params' field is a Lisp list of nodes.  Each node is 
either a simple
             (:include js2-node)
             (:constructor nil)
             (:constructor make-js2-var-decl-node (&key (type js2-VAR)
-                                                       (pos js2-token-beg)
+                                                       (pos 
(js2-current-token-beg))
                                                        len kids
                                                        decl-type)))
   "AST node for a variable declaration list (VAR, CONST or LET).
@@ -3226,7 +3241,7 @@ property is added if the operator follows the operand."
             (:include js2-scope)
             (:constructor nil)
             (:constructor make-js2-let-node (&key (type js2-LETEXPR)
-                                                  (pos js2-token-beg)
+                                                  (pos (js2-current-token-beg))
                                                   len vars body
                                                   lp rp)))
   "AST node for a let expression or a let statement.
@@ -3253,7 +3268,7 @@ Note that a let declaration such as let x=6, y=7 is a 
`js2-var-decl-node'."
             (:include js2-node)
             (:constructor nil)
             (:constructor make-js2-keyword-node (&key type
-                                                      (pos js2-token-beg)
+                                                      (pos 
(js2-current-token-beg))
                                                       (len (- js2-ts-cursor 
pos)))))
   "AST node representing a literal keyword such as `null'.
 Used for `null', `this', `true', `false' and `debugger'.
@@ -3281,7 +3296,7 @@ The node type is set to js2-NULL, js2-THIS, etc.")
             (:include js2-node)
             (:constructor nil)
             (:constructor make-js2-new-node (&key (type js2-NEW)
-                                                  (pos js2-token-beg)
+                                                  (pos (js2-current-token-beg))
                                                   len target
                                                   args initializer
                                                   lp rp)))
@@ -3315,10 +3330,10 @@ The node type is set to js2-NULL, js2-THIS, etc.")
             (:include js2-node)
             (:constructor nil)
             (:constructor make-js2-name-node (&key (type js2-NAME)
-                                                   (pos js2-token-beg)
+                                                   (pos 
(js2-current-token-beg))
                                                    (len (- js2-ts-cursor
-                                                           js2-token-beg))
-                                                   (name js2-ts-string))))
+                                                           
(js2-current-token-beg)))
+                                                   (name 
(js2-current-token-string)))))
   "AST node for a JavaScript identifier"
   name   ; a string
   scope) ; a `js2-scope' (optional, used for codegen)
@@ -3341,11 +3356,12 @@ Returns 0 if NODE is nil or its identifier field is 
nil."
             (:include js2-node)
             (:constructor nil)
             (:constructor make-js2-number-node (&key (type js2-NUMBER)
-                                                     (pos js2-token-beg)
+                                                     (pos 
(js2-current-token-beg))
                                                      (len (- js2-ts-cursor
-                                                             js2-token-beg))
-                                                     (value js2-ts-string)
-                                                     (num-value 
js2-ts-number))))
+                                                             
(js2-current-token-beg)))
+                                                     (value 
(js2-current-token-string))
+                                                     (num-value 
(js2-token-number
+                                                                 
(js2-current-token))))))
   "AST node for a number literal."
   value      ; the original string, e.g. "6.02e23"
   num-value) ; the parsed number value
@@ -3361,9 +3377,9 @@ Returns 0 if NODE is nil or its identifier field is nil."
             (:include js2-node)
             (:constructor nil)
             (:constructor make-js2-regexp-node (&key (type js2-REGEXP)
-                                                     (pos js2-token-beg)
+                                                     (pos 
(js2-current-token-beg))
                                                      (len (- js2-ts-cursor
-                                                             js2-token-beg))
+                                                             
(js2-current-token-beg)))
                                                      value flags)))
   "AST node for a regular expression literal."
   value  ; the regexp string, without // delimiters
@@ -3384,10 +3400,10 @@ Returns 0 if NODE is nil or its identifier field is 
nil."
             (:include js2-node)
             (:constructor nil)
             (:constructor make-js2-string-node (&key (type js2-STRING)
-                                                     (pos js2-token-beg)
+                                                     (pos 
(js2-current-token-beg))
                                                      (len (- js2-ts-cursor
-                                                             js2-token-beg))
-                                                     (value js2-ts-string))))
+                                                             
(js2-current-token-beg)))
+                                                     (value 
(js2-current-token-string)))))
   "String literal.
 Escape characters are not evaluated; e.g. \n is 2 chars in value field.
 You can tell the quote type by looking at the first character."
@@ -3685,7 +3701,7 @@ as opposed to required parens such as those enclosing an 
if-conditional."
             (:include js2-node)
             (:constructor nil)
             (:constructor make-js2-empty-expr-node (&key (type js2-EMPTY)
-                                                         (pos js2-token-beg)
+                                                         (pos 
(js2-current-token-beg))
                                                          len)))
   "AST node for an empty expression.")
 
@@ -3696,7 +3712,7 @@ as opposed to required parens such as those enclosing an 
if-conditional."
             (:include js2-block-node)
             (:constructor nil)
             (:constructor make-js2-xml-node (&key (type js2-XML)
-                                                  (pos js2-token-beg)
+                                                  (pos (js2-current-token-beg))
                                                   len kids)))
   "AST node for initial parse of E4X literals.
 The kids field is a list of XML fragments, each a `js2-string-node' or
@@ -3792,7 +3808,7 @@ expression whose parent is a `js2-xml-dot-query-node'."
             (:include js2-xml-ref-node)
             (:constructor nil)
             (:constructor make-js2-xml-prop-ref-node (&key (type js2-REF_NAME)
-                                                           (pos js2-token-beg)
+                                                           (pos 
(js2-current-token-beg))
                                                            len propname
                                                            namespace at-pos
                                                            colon-pos)))
@@ -3831,7 +3847,7 @@ expression."
             (:include js2-xml-ref-node)
             (:constructor nil)
             (:constructor make-js2-xml-elem-ref-node (&key (type 
js2-REF_MEMBER)
-                                                           (pos js2-token-beg)
+                                                           (pos 
(js2-current-token-beg))
                                                            len expr lb rb
                                                            namespace at-pos
                                                            colon-pos)))
@@ -4983,7 +4999,7 @@ nor always false."
     names)
   "Vector mapping int values to token string names, sans `js2-' prefix.")
 
-(defun js2-token-name (tok)
+(defun js2-tt-name (tok)
   "Return a string name for TOK, a token symbol or code.
 Signals an error if it's not a recognized token."
   (let ((code tok))
@@ -4997,9 +5013,9 @@ Signals an error if it's not a recognized token."
           (aref js2-token-names code)
         (error "Invalid token: %s" code)))))
 
-(defsubst js2-token-sym (tok)
+(defsubst js2-tt-sym (tok)
   "Return symbol for TOK given its code, e.g. 'js2-LP for code 86."
-  (intern (js2-token-name tok)))
+  (intern (js2-tt-name tok)))
 
 (defconst js2-token-codes
   (let ((table (make-hash-table :test 'eq :size 256)))
@@ -5010,24 +5026,24 @@ Signals an error if it's not a recognized token."
     ;; clean up a few that are "wrong" in Rhino's token codes
     (puthash 'js2-DELETE js2-DELPROP table)
     table)
-  "Hashtable mapping token symbols to their bytecodes.")
+  "Hashtable mapping token type symbols to their bytecodes.")
 
-(defsubst js2-token-code (sym)
+(defsubst js2-tt-code (sym)
   "Return code for token symbol SYM, e.g. 86 for 'js2-LP."
   (or (gethash sym js2-token-codes)
       (error "Invalid token symbol: %s " sym)))  ; signal code bug
 
 (defun js2-report-scan-error (msg &optional no-throw beg len)
-  (setq js2-token-end js2-ts-cursor)
+  (setf (js2-token-end (js2-current-token)) js2-ts-cursor)
   (js2-report-error msg nil
-                    (or beg js2-token-beg)
-                    (or len (- js2-token-end js2-token-beg)))
+                    (or beg (js2-current-token-beg))
+                    (or len (js2-current-token-len)))
   (unless no-throw
     (throw 'return js2-ERROR)))
 
 (defun js2-get-string-from-buffer ()
   "Reverse the char accumulator and return it as a string."
-  (setq js2-token-end js2-ts-cursor)
+  (setf (js2-token-end (js2-current-token)) js2-ts-cursor)
   (if js2-ts-string-buffer
       (apply #'string (nreverse js2-ts-string-buffer))
     ""))
@@ -5135,7 +5151,7 @@ Returns nil and consumes nothing if TEST is not the next 
character."
   (let (c)
     (while (not (memq (setq c (js2-get-char)) js2-eol-chars)))
     (js2-unget-char)
-    (setq js2-token-end js2-ts-cursor)))
+    (setf (js2-token-end (js2-current-token)) js2-ts-cursor)))
 
 (defun js2-init-scanner (&optional buf line)
   "Create token stream for BUF starting on LINE.
@@ -5147,14 +5163,14 @@ have simultaneous scanners in a buffer, copy the 
regions to scan
 into temp buffers."
   (with-current-buffer (or buf (current-buffer))
     (setq js2-ts-dirty-line nil
-          js2-ts-regexp-flags nil
-          js2-ts-string ""
-          js2-ts-number nil
           js2-ts-hit-eof nil
           js2-ts-line-start 0
           js2-ts-lineno (or line 1)
           js2-ts-line-end-char -1
           js2-ts-cursor (point-min)
+          js2-ti-tokens (make-vector js2-ti-ntokens nil)
+          js2-ti-tokens-cursor 0
+          js2-ti-lookahead 0
           js2-ts-is-xml-attribute nil
           js2-ts-xml-is-tag-content nil
           js2-ts-xml-open-tags-count 0
@@ -5167,12 +5183,12 @@ into temp buffers."
   ;; Not sure where this function is used in Rhino.  Not tested.
   (if (not js2-debug-print-trees)
       ""
-    (let ((name (js2-token-name token)))
+    (let ((name (js2-tt-name token)))
       (cond
        ((memq token (list js2-STRING js2-REGEXP js2-NAME))
-        (concat name " `" js2-ts-string "'"))
+        (concat name " `" (js2-current-token-string) "'"))
        ((eq token js2-NUMBER)
-        (format "NUMBER %g" js2-ts-number))
+        (format "NUMBER %g" (js2-token-number (js2-current-token))))
        (t
         name)))))
 
@@ -5279,16 +5295,17 @@ Returns a symbol such as 'js2-BREAK, or nil if not 
keyword/reserved."
   (or (gethash s js2-keyword-names)
       (gethash s js2-reserved-word-names)))
 
-(defsubst js2-ts-set-char-token-bounds ()
+(defsubst js2-ts-set-char-token-bounds (token)
   "Used when next token is one character."
-  (setq js2-token-beg (1- js2-ts-cursor)
-        js2-token-end js2-ts-cursor))
+  (setf (js2-token-beg token) (1- js2-ts-cursor)
+        (js2-token-end token) js2-ts-cursor))
 
-(defsubst js2-ts-return (token)
-  "Return an N-character TOKEN from `js2-get-token'.
-Updates `js2-token-end' accordingly."
-  (setq js2-token-end js2-ts-cursor)
-  (throw 'return token))
+(defsubst js2-ts-return (token type)
+  "Update the `end' and `type' slots of TOKEN,
+then throw `return' with value TYPE."
+  (setf (js2-token-end token) js2-ts-cursor
+        (js2-token-type token) type)
+  (throw 'return type))
 
 (defun js2-x-digit-to-int (c accumulator)
   "Build up a hex number.
@@ -5314,10 +5331,70 @@ corresponding number.  Otherwise return -1."
     (logior c (lsh accumulator 4))))
 
 (defun js2-get-token ()
-  "Return next JavaScript token, an int such as js2-RETURN."
+  "If `js2-ti-lookahead' is zero, call scanner to get new token.
+Otherwise, move `js2-ti-tokens-cursor' and return the type of
+next saved token.
+
+This function will not return a newline (js2-EOL) - instead, it
+gobbles newlines until it finds a non-newline token.  Call
+`js2-peek-token-or-eol' when you care about newlines.
+
+This function will also not return a js2-COMMENT.  Instead, it
+records comments found in `js2-scanned-comments'.  If the token
+returned by this function immediately follows a jsdoc comment,
+the token is flagged as such."
+  (if (zerop js2-ti-lookahead)
+      (js2-get-token-internal)
+    (decf js2-ti-lookahead)
+    (setq js2-ti-tokens-cursor (mod (1+ js2-ti-tokens-cursor) js2-ti-ntokens))
+    (let ((tt (js2-current-token-type)))
+      (assert (not (= tt js2-EOL)))
+      tt)))
+
+(defun js2-unget-token ()
+  (assert (< js2-ti-lookahead js2-ti-max-lookahead))
+  (incf js2-ti-lookahead)
+  (setq js2-ti-tokens-cursor (mod (1- js2-ti-tokens-cursor) js2-ti-ntokens)))
+
+(defun js2-get-token-internal ()
+  (let ((tt (js2-get-token-internal-1))      ; call scanner
+        saw-eol
+        face)
+    ;; process comments
+    (while (or (= tt js2-EOL) (= tt js2-COMMENT))
+      (if (= tt js2-EOL)
+          (setq saw-eol t)
+        (setq saw-eol nil)
+        (if js2-record-comments
+          (js2-record-comment)))
+      (setq js2-ti-tokens-cursor (mod (1- js2-ti-tokens-cursor) 
js2-ti-ntokens))
+      (setq tt (js2-get-token-internal-1)))  ; call scanner again
+
+    (when saw-eol
+      (setf (js2-token-follows-eol-p (js2-current-token)) t))
+
+    ;; perform lexical fontification as soon as token is scanned
+    (when js2-parse-ide-mode
+      (cond
+       ((minusp tt)
+        (js2-record-face 'js2-error))
+       ((setq face (aref js2-kwd-tokens tt))
+        (js2-record-face face))
+       ((and (= tt js2-NAME)
+             (equal (js2-current-token-string) "undefined"))
+        (js2-record-face 'font-lock-constant-face))))
+    tt))
+
+(defun js2-get-token-internal-1 ()
+  "Return next JavaScript token type, an int such as js2-RETURN.
+During operation, creates an instance of `js2-token' struct, sets
+its relevant fields and puts it into `js2-ti-tokens'."
   (let (c c1 identifier-start is-unicode-escape-start
         contains-escape escape-val escape-start str result base
-        is-integer quote-char val look-for-slash continue)
+        is-integer quote-char val look-for-slash continue tt
+        (token (js2-new-token 0)))
+   (setq
+    tt
     (catch 'return
       (while t
         ;; Eat whitespace, possibly sensitive to newlines.
@@ -5326,10 +5403,10 @@ corresponding number.  Otherwise return -1."
           (setq c (js2-get-char))
           (cond
            ((eq c js2-EOF_CHAR)
-            (js2-ts-set-char-token-bounds)
+            (js2-ts-set-char-token-bounds token)
             (throw 'return js2-EOF))
            ((eq c ?\n)
-            (js2-ts-set-char-token-bounds)
+            (js2-ts-set-char-token-bounds token)
             (setq js2-ts-dirty-line nil)
             (throw 'return js2-EOL))
            ((not (js2-js-space-p c))
@@ -5337,7 +5414,7 @@ corresponding number.  Otherwise return -1."
                 (setq js2-ts-dirty-line t))
             (setq continue nil))))
         ;; Assume the token will be 1 char - fixed up below.
-        (js2-ts-set-char-token-bounds)
+        (js2-ts-set-char-token-bounds token)
         (when (eq c ?@)
           (throw 'return js2-XMLATTR))
         ;; identifier/keyword/instanceof?
@@ -5404,10 +5481,10 @@ corresponding number.  Otherwise return -1."
                   ;; LET and YIELD are tokens only in 1.7 and later
                   (setq result 'js2-NAME))
               (if (not (eq result 'js2-RESERVED))
-                  (throw 'return (js2-token-code result)))
+                  (throw 'return (js2-tt-code result)))
               (js2-report-warning "msg.reserved.keyword" str)))
           ;; If we want to intern these as Rhino does, just use (intern str)
-          (setq js2-ts-string str)
+          (setf (js2-token-string token) str)
           (throw 'return js2-NAME))     ; end identifier/kwd check
         ;; is it a number?
         (when (or (js2-digit-p c)
@@ -5460,13 +5537,14 @@ corresponding number.  Otherwise return -1."
                     (setq c (js2-get-char))
                     while (js2-digit-p c))))
           (js2-unget-char)
-          (setq js2-ts-string (js2-get-string-from-buffer)
-                js2-ts-number
-                (if (and (eq base 10) (not is-integer))
-                    (string-to-number js2-ts-string)
-                  ;; TODO:  call runtime number-parser.  Some of it is in
-                  ;; js2-util.el, but I need to port 
ScriptRuntime.stringToNumber.
-                  (string-to-number js2-ts-string)))
+          (let ((str (js2-get-string-from-buffer)))
+            (setf (js2-token-string token) str
+                  (js2-token-number token)
+                  (if (and (eq base 10) (not is-integer))
+                      (string-to-number str)
+                    ;; TODO:  call runtime number-parser.  Some of it is in
+                    ;; js2-util.el, but I need to port 
ScriptRuntime.stringToNumber.
+                    (string-to-number str))))
           (throw 'return js2-NUMBER))
         ;; is it a string?
         (when (memq c '(?\" ?\'))
@@ -5482,7 +5560,7 @@ corresponding number.  Otherwise return -1."
               (catch 'continue
                 (when (or (eq c ?\n) (eq c js2-EOF_CHAR))
                   (js2-unget-char)
-                  (setq js2-token-end js2-ts-cursor)
+                  (setf (js2-token-end token) js2-ts-cursor)
                   (js2-report-error "msg.unterminated.string.lit")
                   (throw 'return js2-STRING))
                 (when (eq c ?\\)
@@ -5565,7 +5643,7 @@ corresponding number.  Otherwise return -1."
                        (setq c val)))))
                 (js2-add-to-string c)
                 (setq c (js2-get-char)))))
-          (setq js2-ts-string (js2-get-string-from-buffer))
+          (setf (js2-token-string token) (js2-get-string-from-buffer))
           (throw 'return js2-STRING))
         (case c
           (?\;
@@ -5588,43 +5666,43 @@ corresponding number.  Otherwise return -1."
            (throw 'return js2-HOOK))
           (?:
            (if (js2-match-char ?:)
-               (js2-ts-return js2-COLONCOLON)
+               (js2-ts-return token js2-COLONCOLON)
              (throw 'return js2-COLON)))
           (?.
            (if (js2-match-char ?.)
                (if (js2-match-char ?.)
-                   (js2-ts-return js2-TRIPLEDOT)
-                 (js2-ts-return js2-DOTDOT))
+                   (js2-ts-return token js2-TRIPLEDOT)
+                 (js2-ts-return token js2-DOTDOT))
              (if (js2-match-char ?\()
-                 (js2-ts-return js2-DOTQUERY)
+                 (js2-ts-return token js2-DOTQUERY)
                (throw 'return js2-DOT))))
           (?|
            (if (js2-match-char ?|)
                (throw 'return js2-OR)
              (if (js2-match-char ?=)
-                 (js2-ts-return js2-ASSIGN_BITOR)
+                 (js2-ts-return token js2-ASSIGN_BITOR)
                (throw 'return js2-BITOR))))
           (?^
            (if (js2-match-char ?=)
-               (js2-ts-return js2-ASSIGN_BITOR)
+               (js2-ts-return token js2-ASSIGN_BITOR)
              (throw 'return js2-BITXOR)))
           (?&
            (if (js2-match-char ?&)
                (throw 'return js2-AND)
              (if (js2-match-char ?=)
-                 (js2-ts-return js2-ASSIGN_BITAND)
+                 (js2-ts-return token js2-ASSIGN_BITAND)
                (throw 'return js2-BITAND))))
           (?=
            (if (js2-match-char ?=)
                (if (js2-match-char ?=)
-                   (js2-ts-return js2-SHEQ)
+                   (js2-ts-return token js2-SHEQ)
                  (throw 'return js2-EQ))
              (throw 'return js2-ASSIGN)))
           (?!
            (if (js2-match-char ?=)
                (if (js2-match-char ?=)
-                   (js2-ts-return js2-SHNE)
-                 (js2-ts-return js2-NE))
+                   (js2-ts-return token js2-SHNE)
+                 (js2-ts-return token js2-NE))
              (throw 'return js2-NOT)))
           (?<
            ;; NB:treat HTML begin-comment as comment-till-eol
@@ -5632,46 +5710,46 @@ corresponding number.  Otherwise return -1."
              (when (js2-match-char ?-)
                (when (js2-match-char ?-)
                  (js2-skip-line)
-                 (setq js2-ts-comment-type 'html)
+                 (setf (js2-token-comment-type (js2-current-token)) 'html)
                  (throw 'return js2-COMMENT)))
              (js2-unget-char))
            (if (js2-match-char ?<)
                (if (js2-match-char ?=)
-                   (js2-ts-return js2-ASSIGN_LSH)
-                 (js2-ts-return js2-LSH))
+                   (js2-ts-return token js2-ASSIGN_LSH)
+                 (js2-ts-return token js2-LSH))
              (if (js2-match-char ?=)
-                 (js2-ts-return js2-LE)
+                 (js2-ts-return token js2-LE)
                (throw 'return js2-LT))))
           (?>
            (if (js2-match-char ?>)
                (if (js2-match-char ?>)
                    (if (js2-match-char ?=)
-                       (js2-ts-return js2-ASSIGN_URSH)
-                     (js2-ts-return js2-URSH))
+                       (js2-ts-return token js2-ASSIGN_URSH)
+                     (js2-ts-return token js2-URSH))
                  (if (js2-match-char ?=)
-                     (js2-ts-return js2-ASSIGN_RSH)
-                   (js2-ts-return js2-RSH)))
+                     (js2-ts-return token js2-ASSIGN_RSH)
+                   (js2-ts-return token js2-RSH)))
              (if (js2-match-char ?=)
-                 (js2-ts-return js2-GE)
+                 (js2-ts-return token js2-GE)
                (throw 'return js2-GT))))
           (?*
            (if (js2-match-char ?=)
-               (js2-ts-return js2-ASSIGN_MUL)
+               (js2-ts-return token js2-ASSIGN_MUL)
              (throw 'return js2-MUL)))
           (?/
            ;; is it a // comment?
            (when (js2-match-char ?/)
-             (setq js2-token-beg (- js2-ts-cursor 2))
+             (setf (js2-token-beg token) (- js2-ts-cursor 2))
              (js2-skip-line)
-             (setq js2-ts-comment-type 'line)
+             (setf (js2-token-comment-type token) 'line)
              ;; include newline so highlighting goes to end of window
-             (incf js2-token-end)
+             (incf (js2-token-end token))
              (throw 'return js2-COMMENT))
            ;; is it a /* comment?
            (when (js2-match-char ?*)
-             (setq look-for-slash nil
-                   js2-token-beg (- js2-ts-cursor 2)
-                   js2-ts-comment-type
+             (setf look-for-slash nil
+                   (js2-token-beg token) (- js2-ts-cursor 2)
+                   (js2-token-comment-type token)
                    (if (js2-match-char ?*)
                        (progn
                          (setq look-for-slash t)
@@ -5681,38 +5759,38 @@ corresponding number.  Otherwise return -1."
                (setq c (js2-get-char))
                (cond
                 ((eq c js2-EOF_CHAR)
-                 (setq js2-token-end (1- js2-ts-cursor))
+                 (setf (js2-token-end token) (1- js2-ts-cursor))
                  (js2-report-error "msg.unterminated.comment")
                  (throw 'return js2-COMMENT))
                 ((eq c ?*)
                  (setq look-for-slash t))
                 ((eq c ?/)
                  (if look-for-slash
-                   (js2-ts-return js2-COMMENT)))
+                     (js2-ts-return token js2-COMMENT)))
                 (t
-                 (setq look-for-slash nil
-                       js2-token-end js2-ts-cursor)))))
+                 (setf look-for-slash nil
+                       (js2-token-end token) js2-ts-cursor)))))
            (if (js2-match-char ?=)
-               (js2-ts-return js2-ASSIGN_DIV)
+               (js2-ts-return token js2-ASSIGN_DIV)
              (throw 'return js2-DIV)))
-           (?#
-            (when js2-skip-preprocessor-directives
-              (js2-skip-line)
-              (setq js2-ts-comment-type 'preprocessor
-                    js2-token-end js2-ts-cursor)
-              (throw 'return js2-COMMENT))
-            (throw 'return js2-ERROR))
+          (?#
+           (when js2-skip-preprocessor-directives
+             (js2-skip-line)
+             (setf (js2-token-comment-type token) 'preprocessor
+                   (js2-token-end token) js2-ts-cursor)
+             (throw 'return js2-COMMENT))
+           (throw 'return js2-ERROR))
           (?%
            (if (js2-match-char ?=)
-               (js2-ts-return js2-ASSIGN_MOD)
+               (js2-ts-return token js2-ASSIGN_MOD)
              (throw 'return js2-MOD)))
           (?~
            (throw 'return js2-BITNOT))
           (?+
            (if (js2-match-char ?=)
-               (js2-ts-return js2-ASSIGN_ADD)
+               (js2-ts-return token js2-ASSIGN_ADD)
              (if (js2-match-char ?+)
-                 (js2-ts-return js2-INC)
+                 (js2-ts-return token js2-INC)
                (throw 'return js2-ADD))))
           (?-
            (cond
@@ -5724,29 +5802,30 @@ corresponding number.  Otherwise return -1."
                ;; after line start as comment-until-eol
                (when (js2-match-char ?>)
                  (js2-skip-line)
-                 (setq js2-ts-comment-type 'html)
+                 (setf (js2-token-comment-type (js2-current-token)) 'html)
                  (throw 'return js2-COMMENT)))
              (setq c js2-DEC))
             (t
              (setq c js2-SUB)))
            (setq js2-ts-dirty-line t)
-           (js2-ts-return c))
+           (js2-ts-return token c))
           (otherwise
-           (js2-report-scan-error "msg.illegal.character")))))))
+           (js2-report-scan-error "msg.illegal.character"))))))
+   (setf (js2-token-type token) tt)
+   tt))
 
-(defun js2-read-regexp (start-token)
+(defun js2-read-regexp (start-tt)
   "Called by parser when it gets / or /= in literal context."
   (let (c err
         in-class  ; inside a '[' .. ']' character-class
         flags
-        (continue t))
-    (setq js2-token-beg js2-ts-cursor
-          js2-ts-string-buffer nil
-          js2-ts-regexp-flags nil)
-    (if (eq start-token js2-ASSIGN_DIV)
+        (continue t)
+        (token (js2-new-token 0)))
+    (setq js2-ts-string-buffer nil)
+    (if (eq start-tt js2-ASSIGN_DIV)
         ;; mis-scanned /=
         (js2-add-to-string ?=)
-      (if (not (eq start-token js2-DIV))
+      (if (not (eq start-tt js2-DIV))
           (error "failed assertion")))
     (while (and (not err)
                 (or (/= (setq c (js2-get-char)) ?/)
@@ -5754,9 +5833,9 @@ corresponding number.  Otherwise return -1."
       (cond
        ((or (= c ?\n)
             (= c js2-EOF_CHAR))
-        (setq js2-token-end (1- js2-ts-cursor)
+        (setf (js2-token-end token) (1- js2-ts-cursor)
               err t
-              js2-ts-string (js2-collect-string js2-ts-string-buffer))
+              (js2-token-string token) (js2-collect-string 
js2-ts-string-buffer))
         (js2-report-error "msg.unterminated.re.lit"))
        (t (cond
            ((= c ?\\)
@@ -5781,11 +5860,12 @@ corresponding number.  Otherwise return -1."
       (if (js2-alpha-p (js2-peek-char))
           (js2-report-scan-error "msg.invalid.re.flag" t
                                  js2-ts-cursor 1))
-      (setq js2-ts-string (js2-collect-string js2-ts-string-buffer)
-            js2-ts-regexp-flags (js2-collect-string flags)
-            js2-token-end js2-ts-cursor)
+      (setf (js2-token-string token) (js2-collect-string js2-ts-string-buffer)
+            (js2-token-end token) js2-ts-cursor)
       ;; tell `parse-partial-sexp' to ignore this range of chars
-      (js2-record-text-property js2-token-beg js2-token-end 'syntax-class 
'(2)))))
+      (js2-record-text-property (js2-current-token-beg)
+                                (js2-current-token-end) 'syntax-class '(2)))
+    (js2-collect-string flags)))
 
 (defun js2-get-first-xml-token ()
   (setq js2-ts-xml-open-tags-count 0
@@ -5794,16 +5874,16 @@ corresponding number.  Otherwise return -1."
   (js2-unget-char)
   (js2-get-next-xml-token))
 
-(defun js2-xml-discard-string ()
+(defun js2-xml-discard-string (token)
   "Throw away the string in progress and flag an XML parse error."
-  (setq js2-ts-string-buffer nil
-        js2-ts-string nil)
+  (setf js2-ts-string-buffer nil
+        (js2-token-string token) nil)
   (js2-report-scan-error "msg.XML.bad.form" t))
 
 (defun js2-get-next-xml-token ()
-  (setq js2-ts-string-buffer nil  ; for recording the XML
-        js2-token-beg js2-ts-cursor)
-  (let (c result)
+  (setq js2-ts-string-buffer nil)  ; for recording the XML
+  (let ((token (js2-new-token 0))
+        c result)
     (setq result
           (catch 'return
             (while t
@@ -5826,11 +5906,11 @@ corresponding number.  Otherwise return -1."
                      (decf js2-ts-xml-open-tags-count)))
                   (?{
                    (js2-unget-char)
-                   (setq js2-ts-string (js2-get-string-from-buffer))
+                   (setf (js2-token-string token) (js2-get-string-from-buffer))
                    (throw 'return js2-XML))
                   ((?\' ?\")
                    (js2-add-to-string c)
-                   (unless (js2-read-quoted-string c)
+                   (unless (js2-read-quoted-string c token)
                      (throw 'return js2-ERROR)))
                   (?=
                    (js2-add-to-string c)
@@ -5842,7 +5922,7 @@ corresponding number.  Otherwise return -1."
                    (setq js2-ts-is-xml-attribute nil)))
                 (when (and (not js2-ts-xml-is-tag-content)
                            (zerop js2-ts-xml-open-tags-count))
-                  (setq js2-ts-string (js2-get-string-from-buffer))
+                  (setf (js2-token-string token) (js2-get-string-from-buffer))
                   (throw 'return js2-XMLEND)))
                (t
                 ;; else not tag content
@@ -5862,9 +5942,9 @@ corresponding number.  Otherwise return -1."
                          (if (eq c ?-)
                              (progn
                                (js2-add-to-string c)
-                               (unless (js2-read-xml-comment)
+                               (unless (js2-read-xml-comment token)
                                  (throw 'return js2-ERROR)))
-                           (js2-xml-discard-string)
+                           (js2-xml-discard-string token)
                            (throw 'return js2-ERROR)))
                         (?\[
                          (setq c (js2-get-char)) ;; skip [
@@ -5882,12 +5962,12 @@ corresponding number.  Otherwise return -1."
                                (js2-add-to-string ?T)
                                (js2-add-to-string ?A)
                                (js2-add-to-string ?\[)
-                               (unless (js2-read-cdata)
+                               (unless (js2-read-cdata token)
                                  (throw 'return js2-ERROR)))
-                           (js2-xml-discard-string)
+                           (js2-xml-discard-string token)
                            (throw 'return js2-ERROR)))
                         (t
-                         (unless (js2-read-entity)
+                         (unless (js2-read-entity token)
                            (throw 'return js2-ERROR))))
                       ;; Allow bare CDATA section, e.g.:
                       ;;   let xml = <![CDATA[ foo bar baz ]]>;
@@ -5896,14 +5976,14 @@ corresponding number.  Otherwise return -1."
                      (??
                       (setq c (js2-get-char)) ;; skip ?
                       (js2-add-to-string c)
-                      (unless (js2-read-PI)
+                      (unless (js2-read-PI token)
                         (throw 'return js2-ERROR)))
                      (?/
                       ;; end tag
                       (setq c (js2-get-char)) ;; skip /
                       (js2-add-to-string c)
                       (when (zerop js2-ts-xml-open-tags-count)
-                        (js2-xml-discard-string)
+                        (js2-xml-discard-string token)
                         (throw 'return js2-ERROR))
                       (setq js2-ts-xml-is-tag-content t)
                       (decf js2-ts-xml-open-tags-count))
@@ -5913,24 +5993,25 @@ corresponding number.  Otherwise return -1."
                       (incf js2-ts-xml-open-tags-count))))
                   (?{
                    (js2-unget-char)
-                   (setq js2-ts-string (js2-get-string-from-buffer))
+                   (setf (js2-token-string token) (js2-get-string-from-buffer))
                    (throw 'return js2-XML))
                   (t
                    (js2-add-to-string c))))))))
-    (setq js2-token-end js2-ts-cursor)
+    (setf (js2-token-end token) js2-ts-cursor)
+    (setf (js2-token-type token) result)
     result))
 
-(defun js2-read-quoted-string (quote)
+(defun js2-read-quoted-string (quote token)
   (let (c)
     (catch 'return
       (while (/= (setq c (js2-get-char)) js2-EOF_CHAR)
         (js2-add-to-string c)
         (if (eq c quote)
             (throw 'return t)))
-      (js2-xml-discard-string)  ;; throw away string in progress
+      (js2-xml-discard-string token)  ;; throw away string in progress
       nil)))
 
-(defun js2-read-xml-comment ()
+(defun js2-read-xml-comment (token)
   (let ((c (js2-get-char)))
     (catch 'return
       (while (/= c js2-EOF_CHAR)
@@ -5946,10 +6027,10 @@ corresponding number.  Otherwise return -1."
                   (throw 'return t))
               (throw 'continue nil)))
           (setq c (js2-get-char))))
-      (js2-xml-discard-string)
+      (js2-xml-discard-string token)
       nil)))
 
-(defun js2-read-cdata ()
+(defun js2-read-cdata (token)
   (let ((c (js2-get-char)))
     (catch 'return
       (while (/= c js2-EOF_CHAR)
@@ -5965,10 +6046,10 @@ corresponding number.  Otherwise return -1."
                   (throw 'return t))
               (throw 'continue nil)))
           (setq c (js2-get-char))))
-      (js2-xml-discard-string)
+      (js2-xml-discard-string token)
       nil)))
 
-(defun js2-read-entity ()
+(defun js2-read-entity (token)
   (let ((decl-tags 1)
         c)
     (catch 'return
@@ -5981,10 +6062,10 @@ corresponding number.  Otherwise return -1."
            (decf decl-tags)
            (if (zerop decl-tags)
                (throw 'return t)))))
-      (js2-xml-discard-string)
+      (js2-xml-discard-string token)
       nil)))
 
-(defun js2-read-PI ()
+(defun js2-read-PI (token)
   "Scan an XML processing instruction."
   (let (c)
     (catch 'return
@@ -5994,7 +6075,7 @@ corresponding number.  Otherwise return -1."
           (setq c (js2-get-char))  ;; Skip >
           (js2-add-to-string c)
           (throw 'return t)))
-      (js2-xml-discard-string)
+      (js2-xml-discard-string token)
       nil)))
 
 ;;; Highlighting
@@ -6865,7 +6946,7 @@ i.e. one or more nodes, and an integer position as the 
list tail."
 
 (defmacro js2-record-face (face)
   "Record a style run of FACE for the current token."
-  `(js2-set-face js2-token-beg js2-token-end ,face 'record))
+  `(js2-set-face (js2-current-token-beg) (js2-current-token-end) ,face 
'record))
 
 (defsubst js2-node-end (n)
   "Computes the absolute end of node N.
@@ -6876,117 +6957,66 @@ is only true until the node is added to its parent; 
i.e., while parsing."
 
 (defun js2-record-comment ()
   "Record a comment in `js2-scanned-comments'."
-  (push (make-js2-comment-node :len (- js2-token-end js2-token-beg)
-                               :format js2-ts-comment-type)
+  (push (make-js2-comment-node :len (js2-current-token-len)
+                               :format (js2-token-comment-type
+                                        (js2-current-token)))
         js2-scanned-comments)
   (when js2-parse-ide-mode
-    (js2-record-face (if (eq js2-ts-comment-type 'jsdoc)
+    (js2-record-face (if (eq (js2-token-comment-type
+                              (js2-current-token))
+                             'jsdoc)
                          'font-lock-doc-face
                        'font-lock-comment-face))
-    (when (memq js2-ts-comment-type '(html preprocessor))
+    (when (memq (js2-token-comment-type
+                 (js2-current-token))
+                '(html preprocessor))
       ;; Tell cc-engine the bounds of the comment.
-      (js2-record-text-property js2-token-beg (1- js2-token-end) 'c-in-sws 
t))))
+      (js2-record-text-property (js2-current-token-beg) (1- 
(js2-current-token-end)) 'c-in-sws t))))
 
-;; This function is called depressingly often, so it should be fast.
-;; Most of the time it's looking at the same token it peeked before.
 (defun js2-peek-token ()
-  "Return the next token without consuming it.
-If previous token was consumed, calls scanner to get new token.
-If previous token was -not- consumed, returns it (idempotent).
-
-This function will not return a newline (js2-EOL) - instead, it
-gobbles newlines until it finds a non-newline token, and flags
-that token as appearing just after a newline.
-
-This function will also not return a js2-COMMENT.  Instead, it
-records comments found in `js2-scanned-comments'.  If the token
-returned by this function immediately follows a jsdoc comment,
-the token is flagged as such.
-
-Note that this function always returned the un-flagged token!
-The flags, if any, are saved in `js2-current-flagged-token'."
-  (if (/= js2-current-flagged-token js2-EOF) ; last token not consumed
-      js2-current-token  ; most common case - return already-peeked token
-    (let ((tt (js2-get-token))          ; call scanner
-          saw-eol
-          face)
-      ;; process comments and whitespace
-      (while (or (= tt js2-EOL)
-                 (= tt js2-COMMENT))
-        (if (= tt js2-EOL)
-            (setq saw-eol t)
-          (setq saw-eol nil)
-          (if js2-record-comments
-              (js2-record-comment)))
-        (setq tt (js2-get-token)))  ; call scanner
-      (setq js2-current-token tt
-            js2-current-flagged-token (if saw-eol
-                                          (logior tt js2-ti-after-eol)
-                                        tt))
-      ;; perform lexical fontification as soon as token is scanned
-      (when js2-parse-ide-mode
-        (cond
-         ((minusp tt)
-          (js2-record-face 'js2-error))
-         ((setq face (aref js2-kwd-tokens tt))
-          (js2-record-face face))
-         ((and (= tt js2-NAME)
-               (equal js2-ts-string "undefined"))
-          (js2-record-face 'font-lock-constant-face))))
-      tt)))  ; return unflagged token
-
-(defun js2-peek-flagged-token ()
-  "Return the current token along with any flags set for it."
-  (js2-peek-token)
-  js2-current-flagged-token)
-
-(defsubst js2-consume-token ()
-  (setq js2-current-flagged-token js2-EOF))
-
-(defun js2-next-token ()
-  (prog1
-      (js2-peek-token)
-    (js2-consume-token)))
+  "Return the next token type without consuming it.
+If `js2-ti-lookahead' is positive, return the type of next token
+from `js2-ti-tokens'.  Otherwise, call `js2-get-token'."
+  (if (not (zerop js2-ti-lookahead))
+      (js2-token-type
+       (aref js2-ti-tokens (mod (1+ js2-ti-tokens-cursor) js2-ti-ntokens)))
+    (let ((tt (js2-get-token-internal)))
+      (js2-unget-token)
+      tt)))
 
-(defun js2-next-flagged-token ()
-  (js2-peek-token)
-  (prog1 js2-current-flagged-token
-    (js2-consume-token)))
+(defalias 'js2-next-token 'js2-get-token)
 
 (defun js2-match-token (match)
-  "Consume and return t if next token matches MATCH, a bytecode.
+  "Get next token and return t if it matches MATCH, a bytecode.
 Returns nil and consumes nothing if MATCH is not the next token."
-  (if (/= (js2-peek-token) match)
-      nil
-    (js2-consume-token)
+  (if (/= (js2-get-token) match)
+      (ignore (js2-unget-token))
     t))
 
 (defun js2-match-contextual-kwd (name)
   "Consume and return t if next token is `js2-NAME', and its
-string is NAME.  Returns nil and does nothing otherwise."
-  (if (or (/= (js2-peek-token) js2-NAME)
-          (not (string= js2-ts-string name)))
-      nil
-    (js2-consume-token)
+string is NAME.  Returns nil and keeps current token otherwise."
+  (if (or (/= (js2-get-token) js2-NAME)
+          (not (string= (js2-current-token-string) name)))
+      (progn
+        (js2-unget-token)
+        nil)
     (js2-record-face 'font-lock-keyword-face)
     t))
 
 (defun js2-valid-prop-name-token (tt)
   (or (= tt js2-NAME)
-      (when (and js2-allow-keywords-as-property-names
-                 (plusp tt)
-                 (aref js2-kwd-tokens tt))
-        (js2-save-name-token-data js2-token-beg (js2-token-name tt))
-        t)))
+      (and js2-allow-keywords-as-property-names
+           (plusp tt)
+           (aref js2-kwd-tokens tt))))
 
 (defun js2-match-prop-name ()
   "Consume token and return t if next token is a valid property name.
 It's valid if it's a js2-NAME, or `js2-allow-keywords-as-property-names'
 is non-nil and it's a keyword token."
-  (if (js2-valid-prop-name-token (js2-peek-token))
-      (progn
-        (js2-consume-token)
-        t)
+  (if (js2-valid-prop-name-token (js2-get-token))
+      t
+    (js2-unget-token)
     nil))
 
 (defun js2-must-match-prop-name (msg-id &optional pos len)
@@ -6996,21 +7026,18 @@ is non-nil and it's a keyword token."
     nil))
 
 (defun js2-peek-token-or-eol ()
-  "Return js2-EOL if the current token immediately follows a newline.
-Else returns the current token.  Used in situations where we don't
+  "Return js2-EOL if the next token immediately follows a newline.
+Else returns the next token.  Used in situations where we don't
 consider certain token types valid if they are preceded by a newline.
 One example is the postfix ++ or -- operator, which has to be on the
 same line as its operand."
-  (let ((tt (js2-peek-token)))
-    ;; Check for last peeked token flags
-    (if (js2-flag-set-p js2-current-flagged-token js2-ti-after-eol)
+  (let ((tt (js2-get-token))
+        (follows-eol (js2-token-follows-eol-p (js2-current-token))))
+    (js2-unget-token)
+    (if follows-eol
         js2-EOL
       tt)))
 
-(defun js2-set-check-for-label ()
-  (assert (= (logand js2-current-flagged-token js2-clear-ti-mask) js2-NAME))
-  (js2-set-flag js2-current-flagged-token js2-ti-check-label))
-
 (defun js2-must-match (token msg-id &optional pos len)
   "Match next token to token code TOKEN, or record a syntax error.
 MSG-ID is the error message to report if the match fails.
@@ -7147,18 +7174,17 @@ Scanner should be initialized."
     (setf root (make-js2-ast-root :buffer (buffer-name) :pos pos)
           js2-current-script-or-fn root
           js2-current-scope root
-          js2-current-flagged-token js2-EOF
           js2-nesting-of-function 0
           js2-labeled-stmt nil
           js2-recorded-identifiers nil)  ; for js2-highlight
-    (while (/= (setq tt (js2-peek-token)) js2-EOF)
+    (while (/= (setq tt (js2-get-token)) js2-EOF)
       (if (= tt js2-FUNCTION)
           (progn
-            (js2-consume-token)
             (setq n (js2-parse-function (if js2-called-by-compile-function
                                             'FUNCTION_EXPRESSION
                                           'FUNCTION_STATEMENT))))
         ;; not a function - parse a statement
+        (js2-unget-token)
         (setq n (js2-parse-statement)))
       ;; add function or statement to script
       (setq end (js2-node-end n))
@@ -7180,7 +7206,7 @@ Scanner should be initialized."
     root))
 
 (defun js2-function-parser ()
-  (js2-consume-token)
+  (js2-get-token)
   (js2-parse-function 'FUNCTION_EXPRESSION_STATEMENT))
 
 (defun js2-parse-function-closure-body (fn-node)
@@ -7198,7 +7224,7 @@ Scanner should be initialized."
   (js2-must-match js2-LC "msg.no.brace.body"
                   (js2-node-pos fn-node)
                   (- js2-ts-cursor (js2-node-pos fn-node)))
-  (let ((pos js2-token-beg)         ; LC position
+  (let ((pos (js2-current-token-beg))         ; LC position
         (pn (make-js2-block-node))  ; starts at LC position
         tt
         end)
@@ -7209,12 +7235,12 @@ Scanner should be initialized."
                         (= tt js2-RC)))
           (js2-block-node-push pn (if (/= tt js2-FUNCTION)
                                       (js2-parse-statement)
-                                    (js2-consume-token)
+                                    (js2-get-token)
                                     (js2-parse-function 'FUNCTION_STATEMENT))))
       (decf js2-nesting-of-function))
-    (setq end js2-token-end)  ; assume no curly and leave at current token
+    (setq end (js2-current-token-end))  ; assume no curly and leave at current 
token
     (if (js2-must-match js2-RC "msg.no.brace.after.body" pos)
-        (setq end js2-token-end))
+        (setq end (js2-current-token-end)))
     (setf (js2-node-pos pn) pos
           (js2-node-len pn) (- end pos))
     (setf (js2-function-node-body fn-node) pn)
@@ -7250,13 +7276,14 @@ NODE is either `js2-array-node', `js2-object-node', or 
`js2-name-node'."
 
 (defun js2-parse-function-params (fn-node pos)
   (if (js2-match-token js2-RP)
-      (setf (js2-function-node-rp fn-node) (- js2-token-beg pos))
+      (setf (js2-function-node-rp fn-node) (- (js2-current-token-beg) pos))
     (let (params len param default-found rest-param-at)
       (loop for tt = (js2-peek-token)
             do
             (cond
              ;; destructuring param
              ((or (= tt js2-LB) (= tt js2-LC))
+              (js2-get-token)
               (when default-found
                 (js2-report-error "msg.no.default.after.default.param"))
               (setq param (js2-parse-destruct-primary-expr))
@@ -7274,7 +7301,7 @@ NODE is either `js2-array-node', `js2-object-node', or 
`js2-name-node'."
               (js2-must-match js2-NAME "msg.no.parm")
               (js2-record-face 'js2-function-param)
               (setq param (js2-create-name-node))
-              (js2-define-symbol js2-LP js2-ts-string param)
+              (js2-define-symbol js2-LP (js2-current-token-string) param)
               ;; default parameter value
               (when (or (and default-found
                              (not rest-param-at)
@@ -7285,8 +7312,8 @@ NODE is either `js2-array-node', `js2-object-node', or 
`js2-name-node'."
                         (and (>= js2-language-version 200)
                              (js2-match-token js2-ASSIGN)))
                 (let* ((pos (js2-node-pos param))
-                       (tt js2-current-token)
-                       (op-pos (- js2-token-beg pos))
+                       (tt (js2-current-token-type))
+                       (op-pos (- (js2-current-token-beg) pos))
                        (left param)
                        (right (js2-parse-assign-expr))
                        (len (- (js2-node-end right) pos)))
@@ -7302,7 +7329,7 @@ NODE is either `js2-array-node', `js2-object-node', or 
`js2-name-node'."
             while
             (js2-match-token js2-COMMA))
       (when (js2-must-match js2-RP "msg.no.paren.after.parms")
-        (setf (js2-function-node-rp fn-node) (- js2-token-beg pos)))
+        (setf (js2-function-node-rp fn-node) (- (js2-current-token-beg) pos)))
       (when rest-param-at
         (setf (js2-function-node-rest-p fn-node) t))
       (dolist (p params)
@@ -7318,10 +7345,10 @@ Last token scanned is the close-curly for the function 
body."
                    (js2-function-node-body fn-node))))
     ;; Have it extend from close-curly to bol or beginning of block.
     (let ((pos (save-excursion
-                 (goto-char js2-token-end)
+                 (goto-char (js2-current-token-end))
                  (max (js2-node-abs-pos (js2-function-node-body fn-node))
                       (point-at-bol))))
-          (end js2-token-end))
+          (end (js2-current-token-end)))
       (if (plusp (js2-name-node-length name))
           (js2-add-strict-warning "msg.no.return.value"
                                   (js2-name-node-name name) pos end)
@@ -7329,7 +7356,7 @@ Last token scanned is the close-curly for the function 
body."
 
 (defun js2-parse-function (function-type)
   "Function parser.  FUNCTION-TYPE is a symbol."
-  (let ((pos js2-token-beg)  ; start of 'function' keyword
+  (let ((pos (js2-current-token-beg))  ; start of 'function' keyword
         name name-beg name-end fn-node lp
         (synthetic-type function-type)
         member-expr-node)
@@ -7338,8 +7365,8 @@ Last token scanned is the close-curly for the function 
body."
      ;; function foo(...)
      ((js2-match-token js2-NAME)
       (setq name (js2-create-name-node t)
-            name-beg js2-token-beg
-            name-end js2-token-end)
+            name-beg (js2-current-token-beg)
+            name-end (js2-current-token-end))
       (unless (js2-match-token js2-LP)
         (when js2-allow-member-expr-as-function-name
           ;; function foo.bar(...)
@@ -7358,8 +7385,8 @@ Last token scanned is the close-curly for the function 
body."
         ;; processed as anonymous function
         (setq member-expr-node (js2-parse-member-expr)))
       (js2-must-match js2-LP "msg.no.paren.parms")))
-    (if (= js2-current-token js2-LP)  ; eventually matched LP?
-        (setq lp js2-token-beg))
+    (if (= (js2-current-token-type) js2-LP) ; eventually matched LP?
+        (setq lp (js2-current-token-beg)))
     (if member-expr-node
         (progn
           (setq synthetic-type 'FUNCTION_EXPRESSION)
@@ -7435,7 +7462,7 @@ up to be relative to the parent node.  All children of 
this block
 node are given relative start positions and correct lengths."
   (let ((pn (or parent (make-js2-block-node)))
         tt)
-    (setf (js2-node-pos pn) js2-token-beg)
+    (setf (js2-node-pos pn) (js2-current-token-beg))
     (while (and (> (setq tt (js2-peek-token)) js2-EOF)
                 (/= tt js2-RC))
       (js2-block-node-push pn (js2-parse-statement)))
@@ -7516,14 +7543,13 @@ node are given relative start positions and correct 
lengths."
   (list js2-ERROR js2-EOF js2-RC))
 
 (defun js2-statement-helper ()
-  (let* ((tt (js2-peek-token))
+  (let* ((tt (js2-get-token))
          (first-tt tt)
-         (beg js2-token-beg)
+         (beg (js2-current-token-beg))
          (parser (if (= tt js2-ERROR)
                      #'js2-parse-semi
                    (aref js2-parsers tt)))
-         pn
-         tt-flagged)
+         pn)
     ;; If the statement is set, then it's been told its label by now.
     (and js2-labeled-stmt
          (js2-labeled-stmt-node-stmt js2-labeled-stmt)
@@ -7536,23 +7562,23 @@ node are given relative start positions and correct 
lengths."
     pn))
 
 (defun js2-auto-insert-semicolon (pn)
-  (let* ((tt-flagged (js2-peek-flagged-token))
-         (tt (logand tt-flagged js2-clear-ti-mask))
+  (let* ((tt (js2-get-token))
          (pos (js2-node-pos pn)))
       (cond
        ((= tt js2-SEMI)
-        ;; Consume ';' as a part of expression
-        (js2-consume-token)
         ;; extend the node bounds to include the semicolon.
-        (setf (js2-node-len pn) (- js2-token-end pos)))
+        (setf (js2-node-len pn) (- (js2-current-token-end) pos)))
        ((memq tt js2-autoinsert-semi-and-warn)
+        (js2-unget-token) ; Not ';', do not consume.
         ;; Autoinsert ;
         (js2-parse-warn-missing-semi pos (js2-node-end pn)))
        (t
-        (if (js2-flag-not-set-p tt-flagged js2-ti-after-eol)
+        (if (not (js2-token-follows-eol-p (js2-current-token)))
             ;; Report error if no EOL or autoinsert ';' otherwise
             (js2-report-error "msg.no.semi.stmt")
-          (js2-parse-warn-missing-semi pos (js2-node-end pn)))))))
+          (js2-parse-warn-missing-semi pos (js2-node-end pn)))
+        (js2-unget-token) ; Not ';', do not consume.
+        ))))
 
 (defun js2-parse-condition ()
   "Parse a parenthesized boolean expression, e.g. in an if- or while-stmt.
@@ -7562,10 +7588,10 @@ that must be fixed up by the caller.
 Return value is a list (EXPR LP RP), with absolute paren positions."
   (let (pn lp rp)
     (if (js2-must-match js2-LP "msg.no.paren.cond")
-        (setq lp js2-token-beg))
+        (setq lp (js2-current-token-beg)))
     (setq pn (js2-parse-expr))
     (if (js2-must-match js2-RP "msg.no.paren.after.cond")
-        (setq rp js2-token-beg))
+        (setq rp (js2-current-token-beg)))
     ;; Report strict warning on code like "if (a = 7) ..."
     (if (and js2-strict-cond-assign-warning
              (js2-assign-node-p pn))
@@ -7577,14 +7603,13 @@ Return value is a list (EXPR LP RP), with absolute 
paren positions."
 
 (defun js2-parse-if ()
   "Parser for if-statement.  Last matched token must be js2-IF."
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         cond if-true if-false else-pos end pn)
-    (js2-consume-token)
     (setq cond (js2-parse-condition)
           if-true (js2-parse-statement)
           if-false (if (js2-match-token js2-ELSE)
                        (progn
-                         (setq else-pos (- js2-token-beg pos))
+                         (setq else-pos (- (js2-current-token-beg) pos))
                          (js2-parse-statement)))
           end (js2-node-end (or if-false if-true))
           pn (make-js2-if-node :pos pos
@@ -7600,12 +7625,11 @@ Return value is a list (EXPR LP RP), with absolute 
paren positions."
 
 (defun js2-parse-switch ()
   "Parser for if-statement.  Last matched token must be js2-SWITCH."
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         tt pn discriminant has-default case-expr case-node
         case-pos cases stmt lp rp)
-    (js2-consume-token)
     (if (js2-must-match js2-LP "msg.no.paren.switch")
-        (setq lp js2-token-beg))
+        (setq lp (js2-current-token-beg)))
     (setq discriminant (js2-parse-expr)
           pn (make-js2-switch-node :discriminant discriminant
                                    :pos pos
@@ -7615,15 +7639,15 @@ Return value is a list (EXPR LP RP), with absolute 
paren positions."
     (unwind-protect
         (progn
           (if (js2-must-match js2-RP "msg.no.paren.after.switch")
-              (setf (js2-switch-node-rp pn) (- js2-token-beg pos)))
+              (setf (js2-switch-node-rp pn) (- (js2-current-token-beg) pos)))
           (js2-must-match js2-LC "msg.no.brace.switch")
           (catch 'break
             (while t
               (setq tt (js2-next-token)
-                    case-pos js2-token-beg)
+                    case-pos (js2-current-token-beg))
               (cond
                ((= tt js2-RC)
-                (setf (js2-node-len pn) (- js2-token-end pos))
+                (setf (js2-node-len pn) (- (js2-current-token-end) pos))
                 (throw 'break nil))  ; done
                ((= tt js2-CASE)
                 (setq case-expr (js2-parse-expr))
@@ -7638,7 +7662,7 @@ Return value is a list (EXPR LP RP), with absolute paren 
positions."
                 (js2-report-error "msg.bad.switch")
                 (throw 'break nil)))
               (setq case-node (make-js2-case-node :pos case-pos
-                                                  :len (- js2-token-end 
case-pos)
+                                                  :len (- 
(js2-current-token-end) case-pos)
                                                   :expr case-expr))
               (js2-node-add-children case-node case-expr)
               (while (and (/= (setq tt (js2-peek-token)) js2-RC)
@@ -7658,10 +7682,9 @@ Return value is a list (EXPR LP RP), with absolute paren 
positions."
 
 (defun js2-parse-while ()
   "Parser for while-statement.  Last matched token must be js2-WHILE."
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         (pn (make-js2-while-node))
         cond body)
-    (js2-consume-token)
     (js2-enter-loop pn)
     (unwind-protect
         (progn
@@ -7678,16 +7701,15 @@ Return value is a list (EXPR LP RP), with absolute 
paren positions."
 
 (defun js2-parse-do ()
   "Parser for do-statement.  Last matched token must be js2-DO."
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         (pn (make-js2-do-node))
         cond body end)
-    (js2-consume-token)
     (js2-enter-loop pn)
     (unwind-protect
         (progn
           (setq body (js2-parse-statement))
           (js2-must-match js2-WHILE "msg.no.while.do")
-          (setf (js2-do-node-while-pos pn) (- js2-token-beg pos)
+          (setf (js2-do-node-while-pos pn) (- (js2-current-token-beg) pos)
                 cond (js2-parse-condition)
                 (js2-do-node-condition pn) (car cond)
                 (js2-do-node-body pn) body
@@ -7707,25 +7729,24 @@ Return value is a list (EXPR LP RP), with absolute 
paren positions."
 (defun js2-parse-for ()
   "Parser for for-statement.  Last matched token must be js2-FOR.
 Parses for, for-in, and for each-in statements."
-  (let ((for-pos js2-token-beg)
+  (let ((for-pos (js2-current-token-beg))
         pn is-for-each is-for-in-or-of is-for-of
         in-pos each-pos tmp-pos
         init  ; Node init is also foo in 'foo in object'
         cond  ; Node cond is also object in 'foo in object'
         incr  ; 3rd section of for-loop initializer
         body tt lp rp)
-    (js2-consume-token)
     ;; See if this is a for each () instead of just a for ()
     (when (js2-match-token js2-NAME)
-      (if (string= "each" js2-ts-string)
+      (if (string= "each" (js2-current-token-string))
           (progn
             (setq is-for-each t
-                  each-pos (- js2-token-beg for-pos)) ; relative
+                  each-pos (- (js2-current-token-beg) for-pos)) ; relative
             (js2-record-face 'font-lock-keyword-face))
         (js2-report-error "msg.no.paren.for")))
     (if (js2-must-match js2-LP "msg.no.paren.for")
-        (setq lp (- js2-token-beg for-pos)))
-    (setq tt (js2-peek-token))
+        (setq lp (- (js2-current-token-beg) for-pos)))
+    (setq tt (js2-get-token))
     ;; 'for' makes local scope
     (js2-push-scope (make-js2-scope))
     (unwind-protect
@@ -7733,18 +7754,19 @@ Parses for, for-in, and for each-in statements."
         (let ((js2-in-for-init t))  ; set as dynamic variable
           (cond
            ((= tt js2-SEMI)
+            (js2-unget-token)
             (setq init (make-js2-empty-expr-node)))
            ((or (= tt js2-VAR) (= tt js2-LET))
-            (js2-consume-token)
-            (setq init (js2-parse-variables tt js2-token-beg)))
+            (setq init (js2-parse-variables tt (js2-current-token-beg))))
            (t
+            (js2-unget-token)
             (setq init (js2-parse-expr)))))
       (if (or (js2-match-token js2-IN)
               (and (>= js2-language-version 200)
                    (js2-match-contextual-kwd "of")
                    (setq is-for-of t)))
           (setq is-for-in-or-of t
-                in-pos (- js2-token-beg for-pos)
+                in-pos (- (js2-current-token-beg) for-pos)
                 ;; scope of iteration target object is not the scope we've 
created above.
                 ;; stash current scope temporary.
                 cond (let ((js2-current-scope (js2-scope-parent-scope 
js2-current-scope)))
@@ -7755,12 +7777,12 @@ Parses for, for-in, and for each-in statements."
                        (make-js2-empty-expr-node) ; no loop condition
                      (js2-parse-expr)))
         (js2-must-match js2-SEMI "msg.no.semi.for.cond")
-        (setq tmp-pos js2-token-end
+        (setq tmp-pos (js2-current-token-end)
               incr (if (= (js2-peek-token) js2-RP)
                        (make-js2-empty-expr-node :pos tmp-pos)
                      (js2-parse-expr))))
       (if (js2-must-match js2-RP "msg.no.paren.for.ctrl")
-          (setq rp (- js2-token-beg for-pos)))
+          (setq rp (- (js2-current-token-beg) for-pos)))
       (if (not is-for-in-or-of)
           (setq pn (make-js2-for-node :init init
                                       :condition cond
@@ -7801,7 +7823,7 @@ Parses for, for-in, and for each-in statements."
 
 (defun js2-parse-try ()
   "Parser for try-statement.  Last matched token must be js2-TRY."
-  (let ((try-pos js2-token-beg)
+  (let ((try-pos (js2-current-token-beg))
         try-end
         try-block
         catch-blocks
@@ -7818,7 +7840,6 @@ Parses for, for-in, and for each-in statements."
         block
         lp
         rp)
-    (js2-consume-token)
     (if (/= (js2-peek-token) js2-LC)
         (js2-report-error "msg.no.brace.try"))
     (setq try-block (js2-parse-statement)
@@ -7827,7 +7848,7 @@ Parses for, for-in, and for each-in statements."
     (cond
      ((= peek js2-CATCH)
       (while (js2-match-token js2-CATCH)
-        (setq catch-pos js2-token-beg
+        (setq catch-pos (js2-current-token-beg)
               guard-kwd nil
               catch-cond nil
               lp nil
@@ -7835,27 +7856,28 @@ Parses for, for-in, and for each-in statements."
         (if saw-default-catch
             (js2-report-error "msg.catch.unreachable"))
         (if (js2-must-match js2-LP "msg.no.paren.catch")
-            (setq lp (- js2-token-beg catch-pos)))
+            (setq lp (- (js2-current-token-beg) catch-pos)))
         (js2-push-scope (make-js2-scope))
         (let ((tt (js2-peek-token)))
           (cond
            ;; destructuring pattern
            ;;     catch ({ message, file }) { ... }
            ((or (= tt js2-LB) (= tt js2-LC))
+            (js2-get-token)
             (setq param (js2-parse-destruct-primary-expr))
             (js2-define-destruct-symbols param js2-LET nil))
            ;; simple name
            (t
             (js2-must-match js2-NAME "msg.bad.catchcond")
             (setq param (js2-create-name-node))
-            (js2-define-symbol js2-LET js2-ts-string param))))
+            (js2-define-symbol js2-LET (js2-current-token-string) param))))
         ;; pattern guard
         (if (js2-match-token js2-IF)
-            (setq guard-kwd (- js2-token-beg catch-pos)
+            (setq guard-kwd (- (js2-current-token-beg) catch-pos)
                   catch-cond (js2-parse-expr))
           (setq saw-default-catch t))
         (if (js2-must-match js2-RP "msg.bad.catchcond")
-            (setq rp (- js2-token-beg catch-pos)))
+            (setq rp (- (js2-current-token-beg) catch-pos)))
         (js2-must-match js2-LC "msg.no.brace.catchblock")
         (setq block (js2-parse-statements)
               try-end (js2-node-end block)
@@ -7868,7 +7890,7 @@ Parses for, for-in, and for each-in statements."
                                               :rp rp))
         (js2-pop-scope)
         (if (js2-must-match js2-RC "msg.no.brace.after.body")
-            (setq try-end js2-token-beg))
+            (setq try-end (js2-current-token-beg)))
         (setf (js2-node-len block) (- try-end (js2-node-pos block))
               (js2-node-len catch-node) (- try-end catch-pos))
         (js2-node-add-children catch-node param catch-cond block)
@@ -7879,7 +7901,7 @@ Parses for, for-in, and for each-in statements."
                       (- (setq try-end (js2-node-end try-block))
                          (js2-node-pos try-block)))))
     (when (js2-match-token js2-FINALLY)
-      (setq finally-pos js2-token-beg
+      (setq finally-pos (js2-current-token-beg)
             block (js2-parse-statement)
             try-end (js2-node-end block)
             finally-block (make-js2-finally-node :pos finally-pos
@@ -7899,9 +7921,8 @@ Parses for, for-in, and for each-in statements."
 
 (defun js2-parse-throw ()
   "Parser for throw-statement.  Last matched token must be js2-THROW."
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         expr pn)
-    (js2-consume-token)
     (if (= (js2-peek-token-or-eol) js2-EOL)
         ;; ECMAScript does not allow new lines before throw expression,
         ;; see bug 256617
@@ -7924,19 +7945,18 @@ does not match an existing label, reports an error and 
returns nil."
 
 (defun js2-parse-break ()
   "Parser for break-statement.  Last matched token must be js2-BREAK."
-  (let ((pos js2-token-beg)
-        (end js2-token-end)
+  (let ((pos (js2-current-token-beg))
+        (end (js2-current-token-end))
         break-target ; statement to break from
         break-label  ; in "break foo", name-node representing the foo
         labels       ; matching labeled statement to break to
         pn)
-    (js2-consume-token)  ; `break'
     (when (eq (js2-peek-token-or-eol) js2-NAME)
-      (js2-consume-token)
+      (js2-get-token)
       (setq break-label (js2-create-name-node)
             end (js2-node-end break-label)
             ;; matchJumpLabelName only matches if there is one
-            labels (js2-match-jump-label-name js2-ts-string)
+            labels (js2-match-jump-label-name (js2-current-token-string))
             break-target (if labels (car (js2-labeled-stmt-node-labels 
labels)))))
     (unless (or break-target break-label)
       ;; no break target specified - try for innermost enclosing loop/switch
@@ -7953,19 +7973,18 @@ does not match an existing label, reports an error and 
returns nil."
 
 (defun js2-parse-continue ()
   "Parser for continue-statement.  Last matched token must be js2-CONTINUE."
-  (let ((pos js2-token-beg)
-        (end js2-token-end)
+  (let ((pos (js2-current-token-beg))
+        (end (js2-current-token-end))
         label   ; optional user-specified label, a `js2-name-node'
         labels  ; current matching labeled stmt, if any
         target  ; the `js2-loop-node' target of this continue stmt
         pn)
-    (js2-consume-token)  ; `continue'
     (when (= (js2-peek-token-or-eol) js2-NAME)
-      (js2-consume-token)
+      (js2-get-token)
       (setq label (js2-create-name-node)
             end (js2-node-end label)
             ;; matchJumpLabelName only matches if there is one
-            labels (js2-match-jump-label-name js2-ts-string)))
+            labels (js2-match-jump-label-name (js2-current-token-string))))
     (cond
      ((null labels)  ; no current label to go to
       (if (null js2-loop-set)  ; no loop to continue to
@@ -7985,14 +8004,13 @@ does not match an existing label, reports an error and 
returns nil."
 
 (defun js2-parse-with ()
   "Parser for with-statement.  Last matched token must be js2-WITH."
-  (js2-consume-token)
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         obj body pn lp rp)
     (if (js2-must-match js2-LP "msg.no.paren.with")
-        (setq lp js2-token-beg))
+        (setq lp (js2-current-token-beg)))
     (setq obj (js2-parse-expr))
     (if (js2-must-match js2-RP "msg.no.paren.after.with")
-        (setq rp js2-token-beg))
+        (setq rp (js2-current-token-beg)))
     (let ((js2-nesting-of-with (1+ js2-nesting-of-with)))
         (setq body (js2-parse-statement)))
     (setq pn (make-js2-with-node :pos pos
@@ -8007,11 +8025,10 @@ does not match an existing label, reports an error and 
returns nil."
 (defun js2-parse-const-var ()
   "Parser for var- or const-statement.
 Last matched token must be js2-CONST or js2-VAR."
-  (let ((tt (js2-peek-token))
-        (pos js2-token-beg)
+  (let ((tt (js2-current-token-type))
+        (pos (js2-current-token-beg))
         expr pn)
-    (js2-consume-token)
-    (setq expr (js2-parse-variables tt js2-token-beg)
+    (setq expr (js2-parse-variables tt (js2-current-token-beg))
           pn (make-js2-expr-stmt-node :pos pos
                                       :len (- (js2-node-end expr) pos)
                                       :expr expr))
@@ -8031,8 +8048,7 @@ Last matched token must be js2-CONST or js2-VAR."
 
 (defun js2-parse-let-stmt ()
   "Parser for let-statement.  Last matched token must be js2-LET."
-  (js2-consume-token)
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         expr pn)
     (if (= (js2-peek-token) js2-LP)
         ;; let expression in statement context
@@ -8045,7 +8061,7 @@ Last matched token must be js2-CONST or js2-VAR."
     pn))
 
 (defun js2-parse-ret-yield ()
-  (js2-parse-return-or-yield (js2-peek-token) nil))
+  (js2-parse-return-or-yield (js2-current-token-type) nil))
 
 (defconst js2-parse-return-stmt-enders
   (list js2-SEMI js2-RC js2-EOF js2-EOL js2-ERROR js2-RB js2-RP js2-YIELD))
@@ -8059,8 +8075,8 @@ but not BEFORE."
        (= (logand after mask) mask)))
 
 (defun js2-parse-return-or-yield (tt expr-context)
-  (let ((pos js2-token-beg)
-        (end js2-token-end)
+  (let ((pos (js2-current-token-beg))
+        (end (js2-current-token-end))
         (before js2-end-flags)
         (inside-function (js2-inside-function))
         e ret name)
@@ -8068,7 +8084,6 @@ but not BEFORE."
       (js2-report-error (if (eq tt js2-RETURN)
                             "msg.bad.return"
                           "msg.bad.yield")))
-    (js2-consume-token)
     ;; This is ugly, but we don't want to require a semicolon.
     (unless (memq (js2-peek-token-or-eol) js2-parse-return-stmt-enders)
       (setq e (js2-parse-expr)
@@ -8118,50 +8133,46 @@ but not BEFORE."
     ret))
 
 (defun js2-parse-debugger ()
-  (js2-consume-token)
   (make-js2-keyword-node :type js2-DEBUGGER))
 
 (defun js2-parse-block ()
   "Parser for a curly-delimited statement block.
 Last token matched must be `js2-LC'."
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         (pn (make-js2-scope)))
-    (js2-consume-token)
     (js2-push-scope pn)
     (unwind-protect
         (progn
           (js2-parse-statements pn)
           (js2-must-match js2-RC "msg.no.brace.block")
-          (setf (js2-node-len pn) (- js2-token-end pos)))
+          (setf (js2-node-len pn) (- (js2-current-token-end) pos)))
       (js2-pop-scope))
     pn))
 
 ;; For `js2-ERROR' too, to have a node for error recovery to work on.
 (defun js2-parse-semi ()
   "Parse a statement or handle an error.
-Last matched token is `js2-SEMI' or `js2-ERROR'."
-  (let ((tt (js2-peek-token)) pos len)
-    (js2-consume-token)
+Current token type is `js2-SEMI' or `js2-ERROR'."
+  (let ((tt (js2-current-token-type)) pos len)
     (if (eq tt js2-SEMI)
         (make-js2-empty-expr-node :len 1)
-      (setq pos js2-token-beg
-            len (- js2-token-beg pos))
+      (setq pos (js2-current-token-beg)
+            len (- (js2-current-token-beg) pos))
       (js2-report-error "msg.syntax" nil pos len)
       (make-js2-error-node :pos pos :len len))))
 
 (defun js2-parse-default-xml-namespace ()
   "Parse a `default xml namespace = <expr>' e4x statement."
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         end len expr unary es)
-    (js2-consume-token)
     (js2-must-have-xml)
     (js2-set-requires-activation)
     (setq len (- js2-ts-cursor pos))
     (unless (and (js2-match-token js2-NAME)
-                 (string= js2-ts-string "xml"))
+                 (string= (js2-current-token-string) "xml"))
       (js2-report-error "msg.bad.namespace" nil pos len))
     (unless (and (js2-match-token js2-NAME)
-                 (string= js2-ts-string "namespace"))
+                 (string= (js2-current-token-string) "namespace"))
       (js2-report-error "msg.bad.namespace" nil pos len))
     (unless (js2-match-token js2-ASSIGN)
       (js2-report-error "msg.bad.namespace" nil pos len))
@@ -8178,7 +8189,7 @@ Last matched token is `js2-SEMI' or `js2-ERROR'."
 
 (defun js2-record-label (label bundle)
   ;; current token should be colon that `js2-parse-primary-expr' left untouched
-  (js2-consume-token)
+  (js2-get-token)
   (let ((name (js2-label-node-name label))
         labeled-stmt
         dup)
@@ -8201,29 +8212,26 @@ Called when we found a name in a statement context.  If 
it's a label, we gather
 up any following labels and the next non-label statement into a
 `js2-labeled-stmt-node' bundle and return that.  Otherwise we parse an
 expression and return it wrapped in a `js2-expr-stmt-node'."
-  (let ((pos js2-token-beg)
-        (end js2-token-end)
+  (let ((pos (js2-current-token-beg))
+        (end (js2-current-token-end))
         expr stmt pn bundle
         (continue t))
     ;; set check for label and call down to `js2-parse-primary-expr'
-    (js2-set-check-for-label)
-    (setq expr (js2-parse-expr))
-    (if (/= (js2-node-type expr) js2-LABEL)
-        ;; Parsed non-label expression - wrap with expression stmt.
-        (setq pn (js2-wrap-with-expr-stmt pos expr t))
+    (setq expr (js2-maybe-parse-label))
+    (if (null expr)
+        ;; Parse the non-label expression and wrap with expression stmt.
+        (setq pn (js2-wrap-with-expr-stmt pos (js2-parse-expr) t))
       ;; else parsed a label
       (setq bundle (make-js2-labeled-stmt-node :pos pos))
       (js2-record-label expr bundle)
       ;; look for more labels
-      (while (and continue (= (js2-peek-token) js2-NAME))
-        (js2-set-check-for-label)
-        (setq expr (js2-parse-expr))
-        (if (/= (js2-node-type expr) js2-LABEL)
-            (progn
-              (setq stmt (js2-wrap-with-expr-stmt (js2-node-pos expr) expr t)
-                    continue nil)
-              (js2-auto-insert-semicolon stmt))
-          (js2-record-label expr bundle)))
+      (while (and continue (= (js2-get-token) js2-NAME))
+        (if (setq expr (js2-maybe-parse-label))
+            (js2-record-label expr bundle)
+          (setq expr (js2-parse-expr)
+                stmt (js2-wrap-with-expr-stmt (js2-node-pos expr) expr t)
+                continue nil)
+          (js2-auto-insert-semicolon stmt)))
       ;; no more labels; now parse the labeled statement
       (unwind-protect
             (unless stmt
@@ -8237,9 +8245,33 @@ expression and return it wrapped in a 
`js2-expr-stmt-node'."
       (js2-node-add-children bundle stmt)
       bundle)))
 
+(defun js2-maybe-parse-label ()
+  (assert (= (js2-current-token-type) js2-NAME))
+  (let (label-pos
+        (next-tt (js2-get-token))
+        (label-end (js2-current-token-end)))
+    ;; Do not consume colon, it is used as unwind indicator
+    ;; to return to statementHelper.
+    (js2-unget-token)
+    (if (= next-tt js2-COLON)
+        (prog2
+            (setq label-pos (js2-current-token-beg))
+            (make-js2-label-node :pos label-pos
+                                 :len (- label-end label-pos)
+                                 :name (js2-current-token-string))
+          (js2-set-face label-pos
+                        label-end
+                        'font-lock-variable-name-face 'record))
+      ;; Backtrack from the name token, too.
+      (js2-unget-token)
+      nil)))
+
 (defun js2-parse-expr-stmt ()
   "Default parser in statement context, if no recognized statement found."
-  (js2-wrap-with-expr-stmt js2-token-beg (js2-parse-expr) t))
+  (js2-wrap-with-expr-stmt (js2-current-token-beg)
+                           (progn
+                             (js2-unget-token)
+                             (js2-parse-expr)) t))
 
 (defun js2-parse-variables (decl-type pos)
   "Parse a comma-separated list of variable declarations.
@@ -8264,21 +8296,22 @@ Returns the parsed `js2-var-decl-node' expression node."
     (while continue
       (setq destructuring nil
             name nil
-            tt (js2-peek-token)
-            kid-pos js2-token-beg
-            end js2-token-end
+            tt (js2-get-token)
+            kid-pos (js2-current-token-beg)
+            end (js2-current-token-end)
             init nil)
       (if (or (= tt js2-LB) (= tt js2-LC))
           ;; Destructuring assignment, e.g., var [a, b] = ...
           (setq destructuring (js2-parse-destruct-primary-expr)
                 end (js2-node-end destructuring))
         ;; Simple variable name
+        (js2-unget-token)
         (when (js2-must-match js2-NAME "msg.bad.var")
           (setq name (js2-create-name-node)
-                nbeg js2-token-beg
-                nend js2-token-end
+                nbeg (js2-current-token-beg)
+                nend (js2-current-token-end)
                 end nend)
-          (js2-define-symbol decl-type js2-ts-string name js2-in-for-init)))
+          (js2-define-symbol decl-type (js2-current-token-string) name 
js2-in-for-init)))
       (when (js2-match-token js2-ASSIGN)
         (setq init (js2-parse-assign-expr)
               end (js2-node-end init))
@@ -8317,25 +8350,25 @@ by `js2-parse-variables'."
   (let ((pn (make-js2-let-node :pos pos))
         beg vars body)
     (if (js2-must-match js2-LP "msg.no.paren.after.let")
-        (setf (js2-let-node-lp pn) (- js2-token-beg pos)))
+        (setf (js2-let-node-lp pn) (- (js2-current-token-beg) pos)))
     (js2-push-scope pn)
     (unwind-protect
         (progn
-          (setq vars (js2-parse-variables js2-LET js2-token-beg))
+          (setq vars (js2-parse-variables js2-LET (js2-current-token-beg)))
           (if (js2-must-match js2-RP "msg.no.paren.let")
-              (setf (js2-let-node-rp pn) (- js2-token-beg pos)))
-          (if (and stmt-p (eq (js2-peek-token) js2-LC))
+              (setf (js2-let-node-rp pn) (- (js2-current-token-beg) pos)))
+          (if (and stmt-p (eq (js2-get-token) js2-LC))
               ;; let statement
               (progn
-                (js2-consume-token)
-                (setf beg js2-token-beg  ; position stmt at LC
+                (setf beg (js2-current-token-beg)  ; position stmt at LC
                       body (js2-parse-statements))
                 (js2-must-match js2-RC "msg.no.curly.let")
-                (setf (js2-node-len body) (- js2-token-end beg)
-                      (js2-node-len pn) (- js2-token-end pos)
+                (setf (js2-node-len body) (- (js2-current-token-end) beg)
+                      (js2-node-len pn) (- (js2-current-token-end) pos)
                       (js2-let-node-body pn) body
                       (js2-node-type pn) js2-LET))
             ;; let expression
+            (js2-unget-token)
             (setf body (js2-parse-expr)
                   (js2-node-len pn) (- (js2-node-end body) pos)
                   (js2-let-node-body pn) body))
@@ -8402,7 +8435,7 @@ If NODE is non-nil, it is the AST node associated with 
the symbol."
          op-pos)
     (while (and (not oneshot)
                 (js2-match-token js2-COMMA))
-      (setq op-pos (- js2-token-beg pos))  ; relative
+      (setq op-pos (- (js2-current-token-beg) pos))  ; relative
       (if (= (js2-peek-token) js2-YIELD)
           (js2-report-error "msg.yield.parenthesized"))
       (setq right (js2-parse-assign-expr)
@@ -8417,21 +8450,21 @@ If NODE is non-nil, it is the AST node associated with 
the symbol."
     pn))
 
 (defun js2-parse-assign-expr ()
-  (let ((tt (js2-peek-token))
-        (pos js2-token-beg)
+  (let ((tt (js2-get-token))
+        (pos (js2-current-token-beg))
         pn left right op-pos)
     (if (= tt js2-YIELD)
         (js2-parse-return-or-yield tt t)
       ;; not yield - parse assignment expression
       (setq pn (js2-parse-cond-expr)
-            tt (js2-peek-token))
-      (when (and (<= js2-first-assign tt)
-                 (<= tt js2-last-assign))
+            tt (js2-get-token))
+      (if (not (and (<= js2-first-assign tt)
+                    (<= tt js2-last-assign)))
+          (js2-unget-token)
         ;; tt express assignment (=, |=, ^=, ..., %=)
-        (js2-consume-token)
-        (setq op-pos (- js2-token-beg pos)  ; relative
-              left pn
-              right (js2-parse-assign-expr)
+        (setq op-pos (- (js2-current-token-beg) pos)  ; relative
+              left pn)
+        (setq right (js2-parse-assign-expr)
               pn (make-js2-assign-node :type tt
                                        :pos pos
                                        :len (- (js2-node-end right) pos)
@@ -8446,7 +8479,7 @@ If NODE is non-nil, it is the AST node associated with 
the symbol."
       pn)))
 
 (defun js2-parse-cond-expr ()
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         (pn (js2-parse-or-expr))
         test-expr
         if-true
@@ -8454,10 +8487,10 @@ If NODE is non-nil, it is the AST node associated with 
the symbol."
         q-pos
         c-pos)
     (when (js2-match-token js2-HOOK)
-      (setq q-pos (- js2-token-beg pos)
+      (setq q-pos (- (js2-current-token-beg) pos)
             if-true (js2-parse-assign-expr))
       (js2-must-match js2-COLON "msg.no.colon.cond")
-      (setq c-pos (- js2-token-beg pos)
+      (setq c-pos (- (js2-current-token-beg) pos)
             if-false (js2-parse-assign-expr)
             test-expr pn
             pn (make-js2-cond-node :pos pos
@@ -8475,11 +8508,13 @@ If NODE is non-nil, it is the AST node associated with 
the symbol."
 LEFT is the left-side-expression, already parsed, and the
 binary operator should have just been matched.
 PARSER is a function to call to parse the right operand,
-or a `js2-node' struct if it has already been parsed."
+or a `js2-node' struct if it has already been parsed.
+FIXME: The latter option is unused?"
   (let* ((pos (js2-node-pos left))
-         (op-pos (- js2-token-beg pos))
+         (op-pos (- (js2-current-token-beg) pos))
          (right (if (js2-node-p parser)
                     parser
+                  (js2-get-token)
                   (funcall parser)))
          (pn (make-js2-infix-node :type type
                                   :pos pos
@@ -8536,11 +8571,11 @@ or a `js2-node' struct if it has already been parsed."
 (defun js2-parse-eq-expr ()
   (let ((pn (js2-parse-rel-expr))
         tt)
-    (while (memq (setq tt (js2-peek-token)) js2-parse-eq-ops)
-      (js2-consume-token)
+    (while (memq (setq tt (js2-get-token)) js2-parse-eq-ops)
       (setq pn (js2-make-binary tt
                                 pn
                                 'js2-parse-rel-expr)))
+    (js2-unget-token)
     pn))
 
 (defconst js2-parse-rel-ops
@@ -8551,14 +8586,15 @@ or a `js2-node' struct if it has already been parsed."
         (continue t)
         tt)
     (while continue
-      (setq tt (js2-peek-token))
+      (setq tt (js2-get-token))
       (cond
        ((and js2-in-for-init (= tt js2-IN))
+        (js2-unget-token)
         (setq continue nil))
        ((memq tt js2-parse-rel-ops)
-        (js2-consume-token)
         (setq pn (js2-make-binary tt pn 'js2-parse-shift-expr)))
        (t
+        (js2-unget-token)
         (setq continue nil))))
     pn))
 
@@ -8570,11 +8606,10 @@ or a `js2-node' struct if it has already been parsed."
         tt
         (continue t))
     (while continue
-      (setq tt (js2-peek-token))
+      (setq tt (js2-get-token))
       (if (memq tt js2-parse-shift-ops)
-          (progn
-            (js2-consume-token)
-            (setq pn (js2-make-binary tt pn 'js2-parse-add-expr)))
+          (setq pn (js2-make-binary tt pn 'js2-parse-add-expr))
+        (js2-unget-token)
         (setq continue nil)))
     pn))
 
@@ -8583,11 +8618,10 @@ or a `js2-node' struct if it has already been parsed."
         tt
         (continue t))
     (while continue
-      (setq tt (js2-peek-token))
+      (setq tt (js2-get-token))
       (if (or (= tt js2-ADD) (= tt js2-SUB))
-          (progn
-            (js2-consume-token)
-            (setq pn (js2-make-binary tt pn 'js2-parse-mul-expr)))
+          (setq pn (js2-make-binary tt pn 'js2-parse-mul-expr))
+        (js2-unget-token)
         (setq continue nil)))
     pn))
 
@@ -8599,11 +8633,10 @@ or a `js2-node' struct if it has already been parsed."
         tt
         (continue t))
     (while continue
-      (setq tt (js2-peek-token))
+      (setq tt (js2-get-token))
       (if (memq tt js2-parse-mul-ops)
-          (progn
-            (js2-consume-token)
-            (setq pn (js2-make-binary tt pn 'js2-parse-unary-expr)))
+          (setq pn (js2-make-binary tt pn 'js2-parse-unary-expr))
+        (js2-unget-token)
         (setq continue nil)))
     pn))
 
@@ -8611,7 +8644,7 @@ or a `js2-node' struct if it has already been parsed."
   "Make a unary node of type TYPE.
 PARSER is either a node (for postfix operators) or a function to call
 to parse the operand (for prefix operators)."
-  (let* ((pos js2-token-beg)
+  (let* ((pos (js2-current-token-beg))
          (postfix (js2-node-p parser))
          (expr (if postfix
                    parser
@@ -8620,7 +8653,7 @@ to parse the operand (for prefix operators)."
          pn)
     (if postfix  ; e.g. i++
         (setq pos (js2-node-pos expr)
-              end js2-token-end)
+              end (js2-current-token-end))
       (setq end (js2-node-end expr)))
     (setq pn (make-js2-unary-node :type type
                                   :pos pos
@@ -8642,52 +8675,51 @@ to parse the operand (for prefix operators)."
                       nil beg (- end beg))))
 
 (defun js2-parse-unary-expr ()
-  (let ((tt (js2-peek-token))
+  (let ((tt (js2-current-token-type))
         pn expr beg end)
     (cond
      ((or (= tt js2-VOID)
           (= tt js2-NOT)
           (= tt js2-BITNOT)
           (= tt js2-TYPEOF))
-      (js2-consume-token)
+      (js2-get-token)
       (js2-make-unary tt 'js2-parse-unary-expr))
      ((= tt js2-ADD)
-      (js2-consume-token)
+      (js2-get-token)
       ;; Convert to special POS token in decompiler and parse tree
       (js2-make-unary js2-POS 'js2-parse-unary-expr))
      ((= tt js2-SUB)
-      (js2-consume-token)
+      (js2-get-token)
       ;; Convert to special NEG token in decompiler and parse tree
       (js2-make-unary js2-NEG 'js2-parse-unary-expr))
      ((or (= tt js2-INC)
           (= tt js2-DEC))
-      (js2-consume-token)
+      (js2-get-token)
       (prog1
-          (setq beg js2-token-beg
-                end js2-token-end
+          (setq beg (js2-current-token-beg)
+                end (js2-current-token-end)
                 expr (js2-make-unary tt 'js2-parse-member-expr t))
         (js2-check-bad-inc-dec tt beg end expr)))
      ((= tt js2-DELPROP)
-      (js2-consume-token)
+      (js2-get-token)
       (js2-make-unary js2-DELPROP 'js2-parse-unary-expr))
      ((= tt js2-ERROR)
-      (js2-consume-token)
+      (js2-get-token)
       (make-js2-error-node))  ; try to continue
      ((and (= tt js2-LT)
            js2-compiler-xml-available)
       ;; XML stream encountered in expression.
-      (js2-consume-token)
       (js2-parse-member-expr-tail t (js2-parse-xml-initializer)))
      (t
       (setq pn (js2-parse-member-expr t)
             ;; Don't look across a newline boundary for a postfix incop.
             tt (js2-peek-token-or-eol))
       (when (or (= tt js2-INC) (= tt js2-DEC))
-        (js2-consume-token)
+        (js2-get-token)
         (setf expr pn
               pn (js2-make-unary tt expr))
         (js2-node-set-prop pn 'postfix t)
-        (js2-check-bad-inc-dec tt js2-token-beg js2-token-end pn))
+        (js2-check-bad-inc-dec tt (js2-current-token-beg) 
(js2-current-token-end) pn))
       pn))))
 
 (defun js2-parse-xml-initializer ()
@@ -8710,10 +8742,10 @@ just concatenates everything and makes a new XML or 
XMLList out of it."
         (setq tt (js2-get-next-xml-token)))
       (cond
        ;; js2-XML means we found a {expr} in the XML stream.
-       ;; The js2-ts-string is the XML up to the left-curly.
+       ;; The token string is the XML up to the left-curly.
        ((= tt js2-XML)
-        (push (make-js2-string-node :pos js2-token-beg
-                                    :len (- js2-ts-cursor js2-token-beg))
+        (push (make-js2-string-node :pos (js2-current-token-beg)
+                                    :len (- js2-ts-cursor 
(js2-current-token-beg)))
               kids)
         (js2-must-match js2-LC "msg.syntax")
         (setq expr-pos js2-ts-cursor
@@ -8728,8 +8760,8 @@ just concatenates everything and makes a new XML or 
XMLList out of it."
         (push pn kids))
        ;; a js2-XMLEND token means we hit the final close-tag.
        ((= tt js2-XMLEND)
-        (push (make-js2-string-node :pos js2-token-beg
-                                    :len (- js2-ts-cursor js2-token-beg))
+        (push (make-js2-string-node :pos (js2-current-token-beg)
+                                    :len (- js2-ts-cursor 
(js2-current-token-beg)))
               kids)
         (dolist (kid (nreverse kids))
           (js2-block-node-push pn-xml kid))
@@ -8757,13 +8789,13 @@ Returns the list in reverse order.  Consumes the 
right-paren token."
       result)))
 
 (defun js2-parse-member-expr (&optional allow-call-syntax)
-  (let ((tt (js2-peek-token))
+  (let ((tt (js2-current-token-type))
         pn pos target args beg end init tail)
     (if (/= tt js2-NEW)
         (setq pn (js2-parse-primary-expr))
       ;; parse a 'new' expression
-      (js2-consume-token)
-      (setq pos js2-token-beg
+      (js2-get-token)
+      (setq pos (js2-current-token-beg)
             beg pos
             target (js2-parse-member-expr)
             end (js2-node-end target)
@@ -8774,10 +8806,10 @@ Returns the list in reverse order.  Consumes the 
right-paren token."
       (when (js2-match-token js2-LP)
         ;; Add the arguments to pn, if any are supplied.
         (setf beg pos  ; start of "new" keyword
-              pos js2-token-beg
+              pos (js2-current-token-beg)
               args (nreverse (js2-parse-argument-list))
               (js2-new-node-args pn) args
-              end js2-token-end
+              end (js2-current-token-end)
               (js2-new-node-lp pn) (- pos beg)
               (js2-new-node-rp pn) (- end 1 beg))
         (apply #'js2-node-add-children pn args))
@@ -8799,7 +8831,7 @@ Returns an expression tree that includes PN, the parent 
node."
         tt
         (continue t))
     (while continue
-      (setq tt (js2-peek-token))
+      (setq tt (js2-get-token))
       (cond
        ((or (= tt js2-DOT) (= tt js2-DOTDOT))
         (setq pn (js2-parse-property-access tt pn)))
@@ -8810,8 +8842,10 @@ Returns an expression tree that includes PN, the parent 
node."
        ((= tt js2-LP)
         (if allow-call-syntax
             (setq pn (js2-parse-function-call pn))
+          (js2-unget-token)
           (setq continue nil)))
        (t
+        (js2-unget-token)
         (setq continue nil))))
     (if (>= js2-highlight-level 2)
         (js2-parse-highlight-member-expr-node pn))
@@ -8822,10 +8856,9 @@ Returns an expression tree that includes PN, the parent 
node."
 Last token parsed must be `js2-DOTQUERY'."
   (let ((pos (js2-node-pos pn))
         op-pos expr end)
-    (js2-consume-token)
     (js2-must-have-xml)
     (js2-set-requires-activation)
-    (setq op-pos js2-token-beg
+    (setq op-pos (js2-current-token-beg)
           expr (js2-parse-expr)
           end (js2-node-end expr)
           pn (make-js2-xml-dot-query-node :left pn
@@ -8836,27 +8869,26 @@ Last token parsed must be `js2-DOTQUERY'."
                            (js2-xml-dot-query-node-left pn)
                            (js2-xml-dot-query-node-right pn))
     (if (js2-must-match js2-RP "msg.no.paren")
-        (setf (js2-xml-dot-query-node-rp pn) js2-token-beg
-              end js2-token-end))
+        (setf (js2-xml-dot-query-node-rp pn) (js2-current-token-beg)
+              end (js2-current-token-end)))
     (setf (js2-node-len pn) (- end pos))
     pn))
 
 (defun js2-parse-element-get (pn)
   "Parse an element-get expression, e.g. foo[bar].
 Last token parsed must be `js2-RB'."
-  (let ((lb js2-token-beg)
+  (let ((lb (js2-current-token-beg))
         (pos (js2-node-pos pn))
         rb expr)
-    (js2-consume-token)
     (setq expr (js2-parse-expr))
     (if (js2-must-match js2-RB "msg.no.bracket.index")
-        (setq rb js2-token-beg))
+        (setq rb (js2-current-token-beg)))
     (setq pn (make-js2-elem-get-node :target pn
                                      :pos pos
                                      :element expr
                                      :lb (js2-relpos lb pos)
                                      :rb (js2-relpos rb pos)
-                                     :len (- js2-token-end pos)))
+                                     :len (- (js2-current-token-end) pos)))
     (js2-node-add-children pn
                            (js2-elem-get-node-target pn)
                            (js2-elem-get-node-element pn))
@@ -8865,14 +8897,13 @@ Last token parsed must be `js2-RB'."
 (defun js2-parse-function-call (pn)
   (let (args
         (pos (js2-node-pos pn)))
-    (js2-consume-token)
     (setq pn (make-js2-call-node :pos pos
                                  :target pn
-                                 :lp (- js2-token-beg pos)))
+                                 :lp (- (js2-current-token-beg) pos)))
     (js2-node-add-children pn (js2-call-node-target pn))
     ;; Add the arguments to pn, if any are supplied.
     (setf args (nreverse (js2-parse-argument-list))
-          (js2-call-node-rp pn) (- js2-token-beg pos)
+          (js2-call-node-rp pn) (- (js2-current-token-beg) pos)
           (js2-call-node-args pn) args)
     (apply #'js2-node-add-children pn args)
     (setf (js2-node-len pn) (- js2-ts-cursor pos))
@@ -8881,12 +8912,11 @@ Last token parsed must be `js2-RB'."
 (defun js2-parse-property-access (tt pn)
   "Parse a property access, XML descendants access, or XML attr access."
   (let ((member-type-flags 0)
-        (dot-pos js2-token-beg)
+        (dot-pos (js2-current-token-beg))
         (dot-len (if (= tt js2-DOTDOT) 2 1))
         name
         ref  ; right side of . or .. operator
         result)
-    (js2-consume-token)
     (when (= tt js2-DOTDOT)
       (js2-must-have-xml)
       (setq member-type-flags js2-descendants-flag))
@@ -8895,10 +8925,9 @@ Last token parsed must be `js2-RB'."
           (js2-must-match-prop-name "msg.no.name.after.dot")
           (setq name (js2-create-name-node t js2-GETPROP)
                 result (make-js2-prop-get-node :left pn
-                                               :pos js2-token-beg
+                                               :pos (js2-current-token-beg)
                                                :right name
-                                               :len (- js2-token-end
-                                                       js2-token-beg)))
+                                               :len (js2-current-token-len)))
           (js2-node-add-children result pn name)
           result)
       ;; otherwise look for XML operators
@@ -8912,14 +8941,12 @@ Last token parsed must be `js2-RB'."
       (cond
        ;; needed for generator.throw()
        ((= tt js2-THROW)
-        (js2-save-name-token-data js2-token-beg "throw")
-        (setq ref (js2-parse-property-name nil js2-ts-string 
member-type-flags)))
+        (setq ref (js2-parse-property-name nil nil member-type-flags)))
        ;; handles: name, ns::name, ns::*, ns::[expr]
        ((js2-valid-prop-name-token tt)
-        (setq ref (js2-parse-property-name -1 js2-ts-string 
member-type-flags)))
+        (setq ref (js2-parse-property-name -1 nil member-type-flags)))
        ;; handles: *, *::name, *::*, *::[expr]
        ((= tt js2-MUL)
-        (js2-save-name-token-data js2-token-beg "*")
         (setq ref (js2-parse-property-name nil "*" member-type-flags)))
        ;; handles: '@attr', '@ns::attr', '@ns::*', '@ns::[expr]', etc.
        ((= tt js2-XMLATTR)
@@ -8946,23 +8973,21 @@ This includes expressions of the forms:
 
 Called if we peeked an '@' token."
   (let ((tt (js2-next-token))
-        (at-pos js2-token-beg))
+        (at-pos (js2-current-token-beg)))
     (cond
      ;; handles: @name, @ns::name, @ns::*, @ns::[expr]
      ((js2-valid-prop-name-token tt)
-      (js2-parse-property-name at-pos js2-ts-string 0))
+      (js2-parse-property-name at-pos nil 0))
      ;; handles: @*, @*::name, @*::*, @*::[expr]
      ((= tt js2-MUL)
-      (js2-save-name-token-data js2-token-beg "*")
-      (js2-parse-property-name js2-token-beg "*" 0))
+      (js2-parse-property-name (js2-current-token-beg) "*" 0))
      ;; handles @[expr]
      ((= tt js2-LB)
       (js2-parse-xml-elem-ref at-pos))
      (t
       (js2-report-error "msg.no.name.after.xmlAttr")
       ;; Avoid cascaded errors that happen if we make an error node here.
-      (js2-save-name-token-data js2-token-beg "")
-      (js2-parse-property-name js2-token-beg "" 0)))))
+      (js2-parse-property-name (js2-current-token-beg) "" 0)))))
 
 (defun js2-parse-property-name (at-pos s member-type-flags)
   "Check if :: follows name in which case it becomes qualified name.
@@ -8974,14 +8999,14 @@ MEMBER-TYPE-FLAGS is a bit set tracking whether we're a 
'.' or '..' child.
 Returns a `js2-xml-ref-node' if it's an attribute access, a child of a '..'
 operator, or the name is followed by ::.  For a plain name, returns a
 `js2-name-node'.  Returns a `js2-error-node' for malformed XML expressions."
-  (let ((pos (or at-pos js2-token-beg))
+  (let ((pos (or at-pos (js2-current-token-beg)))
         colon-pos
-        (name (js2-create-name-node t js2-current-token))
+        (name (js2-create-name-node t (js2-current-token-type) s))
         ns tt ref pn)
     (catch 'return
       (when (js2-match-token js2-COLONCOLON)
         (setq ns name
-              colon-pos js2-token-beg
+              colon-pos (js2-current-token-beg)
               tt (js2-next-token))
         (cond
          ;; handles name::name
@@ -8989,8 +9014,7 @@ operator, or the name is followed by ::.  For a plain 
name, returns a
           (setq name (js2-create-name-node)))
          ;; handles name::*
          ((= tt js2-MUL)
-          (js2-save-name-token-data js2-token-beg "*")
-          (setq name (js2-create-name-node)))
+          (setq name (js2-create-name-node nil nil "*")))
          ;; handles name::[expr]
          ((= tt js2-LB)
           (throw 'return (js2-parse-xml-elem-ref at-pos ns colon-pos)))
@@ -9010,15 +9034,15 @@ operator, or the name is followed by ::.  For a plain 
name, returns a
 (defun js2-parse-xml-elem-ref (at-pos &optional namespace colon-pos)
   "Parse the [expr] portion of an xml element reference.
 For instance, @[expr], @*::[expr], or ns::[expr]."
-  (let* ((lb js2-token-beg)
+  (let* ((lb (js2-current-token-beg))
          (pos (or at-pos lb))
          rb
          (expr (js2-parse-expr))
          (end (js2-node-end expr))
          pn)
     (if (js2-must-match js2-RB "msg.no.bracket.index")
-        (setq rb js2-token-beg
-              end js2-token-end))
+        (setq rb (js2-current-token-beg)
+              end (js2-current-token-end)))
     (prog1
         (setq pn
               (make-js2-xml-elem-ref-node :pos pos
@@ -9039,14 +9063,13 @@ For instance, @[expr], @*::[expr], or ns::[expr]."
   "Parse a literal (leaf) expression of some sort.
 Includes complex literals such as functions, object-literals,
 array-literals, array comprehensions and regular expressions."
-  (let ((tt-flagged (js2-next-flagged-token))
-        pn      ; parent node  (usually return value)
+  (let (pn      ; parent node  (usually return value)
         tt
         px-pos  ; paren-expr pos
         len
         flags   ; regexp flags
         expr)
-    (setq tt js2-current-token)
+    (setq tt (js2-current-token-type))
     (cond
      ((= tt js2-FUNCTION)
       (js2-parse-function 'FUNCTION_EXPRESSION))
@@ -9055,21 +9078,21 @@ array-literals, array comprehensions and regular 
expressions."
      ((= tt js2-LC)
       (js2-parse-object-literal))
      ((= tt js2-LET)
-      (js2-parse-let js2-token-beg))
+      (js2-parse-let (js2-current-token-beg)))
      ((= tt js2-LP)
-      (setq px-pos js2-token-beg
+      (setq px-pos (js2-current-token-beg)
             expr (js2-parse-expr))
       (js2-must-match js2-RP "msg.no.paren")
       (setq pn (make-js2-paren-node :pos px-pos
                                     :expr expr
-                                    :len (- js2-token-end px-pos)))
+                                    :len (- (js2-current-token-end) px-pos)))
       (js2-node-add-children pn (js2-paren-node-expr pn))
       pn)
      ((= tt js2-XMLATTR)
       (js2-must-have-xml)
       (js2-parse-attribute-access))
      ((= tt js2-NAME)
-      (js2-parse-name tt-flagged tt))
+      (js2-parse-name tt))
      ((= tt js2-NUMBER)
       (make-js2-number-node))
      ((= tt js2-STRING)
@@ -9078,14 +9101,12 @@ array-literals, array comprehensions and regular 
expressions."
         (js2-record-face 'font-lock-string-face)))
      ((or (= tt js2-DIV) (= tt js2-ASSIGN_DIV))
       ;; Got / or /= which in this context means a regexp literal
-      (setq px-pos js2-token-beg)
-      (js2-read-regexp tt)
-      (setq flags js2-ts-regexp-flags
-            js2-ts-regexp-flags nil)
+      (setq px-pos (js2-current-token-beg))
+      (setq flags (js2-read-regexp tt))
       (prog1
           (make-js2-regexp-node :pos px-pos
                                 :len (- js2-ts-cursor px-pos)
-                                :value js2-ts-string
+                                :value (js2-current-token-string)
                                 :flags flags)
         (js2-set-face px-pos js2-ts-cursor 'font-lock-string-face 'record)
         (js2-record-text-property px-pos js2-ts-cursor 'syntax-table '(2))))
@@ -9109,32 +9130,15 @@ array-literals, array comprehensions and regular 
expressions."
       (js2-report-error "msg.syntax")
       (make-js2-error-node)))))
 
-(defun js2-parse-name (tt-flagged tt)
-  (let ((name js2-ts-string)
-        (name-pos js2-token-beg)
+(defun js2-parse-name (tt)
+  (let ((name (js2-current-token-string))
         node)
-    (if (and (js2-flag-set-p tt-flagged js2-ti-check-label)
-             (= (js2-peek-token) js2-COLON))
-        (prog1
-            ;; Do not consume colon, it is used as unwind indicator
-            ;; to return to statementHelper.
-            (make-js2-label-node :pos name-pos
-                                 :len (- js2-token-end name-pos)
-                                 :name name)
-          (js2-set-face name-pos
-                        js2-token-end
-                        'font-lock-variable-name-face 'record))
-      ;; Otherwise not a label, just a name.  Unfortunately peeking
-      ;; the next token to check for a colon has biffed js2-token-beg
-      ;; and js2-token-end.  We store the name's bounds in buffer vars
-      ;; and `js2-create-name-node' uses them.
-      (js2-save-name-token-data name-pos name)
-      (setq node (if js2-compiler-xml-available
-                     (js2-parse-property-name nil name 0)
-                   (js2-create-name-node 'check-activation)))
-      (if js2-highlight-external-variables
-          (js2-record-name-node node))
-      node)))
+    (setq node (if js2-compiler-xml-available
+                   (js2-parse-property-name nil name 0)
+                 (js2-create-name-node 'check-activation nil name)))
+    (if js2-highlight-external-variables
+        (js2-record-name-node node))
+    node))
 
 (defun js2-parse-warn-trailing-comma (msg pos elems comma-pos)
   (js2-add-strict-warning
@@ -9150,20 +9154,19 @@ array-literals, array comprehensions and regular 
expressions."
    comma-pos))
 
 (defun js2-parse-array-literal ()
-  (let ((pos js2-token-beg)
-        (end js2-token-end)
+  (let ((pos (js2-current-token-beg))
+        (end (js2-current-token-end))
         (after-lb-or-comma t)
         after-comma tt elems pn
         (continue t))
     (unless js2-is-in-destructuring
         (js2-push-scope (make-js2-scope))) ; for array comp
     (while continue
-      (setq tt (js2-peek-token))
+      (setq tt (js2-get-token))
       (cond
        ;; comma
        ((= tt js2-COMMA)
-        (js2-consume-token)
-        (setq after-comma js2-token-end)
+        (setq after-comma (js2-current-token-end))
         (if (not after-lb-or-comma)
             (setq after-lb-or-comma t)
           (push nil elems)))
@@ -9171,10 +9174,9 @@ array-literals, array comprehensions and regular 
expressions."
        ((or (= tt js2-RB)
             (= tt js2-EOF))  ; prevent infinite loop
         (if (= tt js2-EOF)
-            (js2-report-error "msg.no.bracket.arg" nil pos)
-          (js2-consume-token))
+            (js2-report-error "msg.no.bracket.arg" nil pos))
         (setq continue nil
-              end js2-token-end
+              end (js2-current-token-end)
               pn (make-js2-array-node :pos pos
                                       :len (- js2-ts-cursor pos)
                                       :elems (nreverse elems)))
@@ -9190,7 +9192,6 @@ array-literals, array comprehensions and regular 
expressions."
                   ;; [a, b, c] | {a, b, c} | {a:x, b:y, c:z} | a
                   (js2-parse-destruct-primary-expr)
                 ;; invalid pattern
-                (js2-consume-token)
                 (js2-report-error "msg.bad.var")
                 (make-js2-error-node))
               elems)
@@ -9202,6 +9203,7 @@ array-literals, array comprehensions and regular 
expressions."
              (not after-lb-or-comma) ; "for" can't follow a comma
              elems                   ; must have at least 1 element
              (not (cdr elems)))      ; but no 2nd element
+        (js2-unget-token)
         (setf continue nil
               pn (js2-parse-array-comprehension (car elems) pos)))
 
@@ -9209,6 +9211,7 @@ array-literals, array comprehensions and regular 
expressions."
        (t
         (unless after-lb-or-comma
           (js2-report-error "msg.no.bracket.arg"))
+        (js2-unget-token)
         (push (js2-parse-assign-expr) elems)
         (setq after-lb-or-comma nil
               after-comma nil))))
@@ -9222,7 +9225,7 @@ EXPR is the first expression after the opening 
left-bracket.
 POS is the beginning of the LB token preceding EXPR.
 We should have just parsed the 'for' keyword before calling this function."
   (let (loops loop first prev filter if-pos result)
-    (while (= (js2-peek-token) js2-FOR)
+    (while (= (js2-get-token) js2-FOR)
       (let ((prev (car loops))) ; rearrange scope chain
         (push (setq loop (js2-parse-array-comp-loop)) loops)
         (if prev ; each loop is parent scope to the next one
@@ -9230,11 +9233,12 @@ We should have just parsed the 'for' keyword before 
calling this function."
           ; first loop takes expr scope's parent
           (setf (js2-scope-parent-scope (setq first loop))
                 (js2-scope-parent-scope js2-current-scope)))))
+    (js2-unget-token)
     ;; set expr scope's parent to the last loop
     (setf (js2-scope-parent-scope js2-current-scope) (car loops))
-    (when (= (js2-peek-token) js2-IF)
-      (js2-consume-token)
-      (setq if-pos (- js2-token-beg pos)  ; relative
+    (if (/= (js2-get-token) js2-IF)
+        (js2-unget-token)
+      (setq if-pos (- (js2-current-token-beg) pos)  ; relative
             filter (js2-parse-condition)))
     (js2-must-match js2-RB "msg.no.bracket.arg" pos)
     (setq result (make-js2-array-comp-node :pos pos
@@ -9253,26 +9257,27 @@ We should have just parsed the 'for' keyword before 
calling this function."
 (defun js2-parse-array-comp-loop ()
   "Parse a 'for [each] (foo [in|of] bar)' expression in an Array comprehension.
 Last token peeked should be the initial FOR."
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         (pn (make-js2-array-comp-loop-node))
         tt iter obj foreach-p forof-p in-pos each-pos lp rp)
-    (assert (= (js2-next-token) js2-FOR))  ; consumes token
+    (assert (= (js2-current-token-type) js2-FOR))
     (js2-push-scope pn)
     (unwind-protect
         (progn
           (when (js2-match-token js2-NAME)
-            (if (string= js2-ts-string "each")
+            (if (string= (js2-current-token-string) "each")
                 (progn
                   (setq foreach-p t
-                        each-pos (- js2-token-beg pos)) ; relative
+                        each-pos (- (js2-current-token-beg) pos)) ; relative
                   (js2-record-face 'font-lock-keyword-face))
               (js2-report-error "msg.no.paren.for")))
           (if (js2-must-match js2-LP "msg.no.paren.for")
-              (setq lp (- js2-token-beg pos)))
+              (setq lp (- (js2-current-token-beg) pos)))
           (setq tt (js2-peek-token))
           (cond
            ((or (= tt js2-LB)
                 (= tt js2-LC))
+            (js2-get-token)
             (setq iter (js2-parse-destruct-primary-expr))
             (js2-define-destruct-symbols iter js2-LET
                                          'font-lock-variable-name-face t))
@@ -9288,11 +9293,11 @@ Last token peeked should be the initial FOR."
                   (and (>= js2-language-version 200)
                        (js2-match-contextual-kwd "of")
                        (setq forof-p t)))
-              (setq in-pos (- js2-token-beg pos))
+              (setq in-pos (- (js2-current-token-beg) pos))
             (js2-report-error "msg.in.after.for.name"))
           (setq obj (js2-parse-expr))
           (if (js2-must-match js2-RP "msg.no.paren.for.ctrl")
-              (setq rp (- js2-token-beg pos)))
+              (setq rp (- (js2-current-token-beg) pos)))
           (setf (js2-node-pos pn) pos
                 (js2-node-len pn) (- js2-ts-cursor pos)
                 (js2-array-comp-loop-node-iterator pn) iter
@@ -9308,11 +9313,11 @@ Last token peeked should be the initial FOR."
     pn))
 
 (defun js2-parse-object-literal ()
-  (let ((pos js2-token-beg)
+  (let ((pos (js2-current-token-beg))
         tt elems result after-comma
         (continue t))
     (while continue
-      (setq tt (js2-peek-token))
+      (setq tt (js2-get-token))
       (cond
        ;; {foo: ...}, {'foo': ...}, {foo, bar, ...},
        ;; {get foo() {...}}, or {set foo(x) {...}}
@@ -9326,11 +9331,11 @@ Last token peeked should be the initial FOR."
           (push result elems)))
        ;; {12: x} or {10.7: x}
        ((= tt js2-NUMBER)
-        (js2-consume-token)
         (setq after-comma nil)
         (push (js2-parse-plain-property (make-js2-number-node)) elems))
        ;; trailing comma
        ((= tt js2-RC)
+        (js2-unget-token)
         (setq continue nil)
         (if after-comma
             (js2-parse-warn-trailing-comma "msg.extra.trailing.comma"
@@ -9340,7 +9345,7 @@ Last token peeked should be the initial FOR."
         (unless js2-recover-from-parse-errors
           (setq continue nil))))         ; end switch
       (if (js2-match-token js2-COMMA)
-          (setq after-comma js2-token-end)
+          (setq after-comma (js2-current-token-end))
         (setq continue nil)))           ; end loop
     (js2-must-match js2-RC "msg.no.brace.prop")
     (setq result (make-js2-object-node :pos pos
@@ -9352,21 +9357,20 @@ Last token peeked should be the initial FOR."
 (defun js2-parse-named-prop (tt)
   "Parse a name, string, or getter/setter object property.
 When `js2-is-in-destructuring' is t, forms like {a, b, c} will be permitted."
-  (js2-consume-token)
   (let ((string-prop (and (= tt js2-STRING)
                           (make-js2-string-node)))
         expr
-        (ppos js2-token-beg)
-        (pend js2-token-end)
+        (ppos (js2-current-token-beg))
+        (pend (js2-current-token-end))
         (name (js2-create-name-node))
-        (prop js2-ts-string))
+        (prop (js2-current-token-string)))
     (cond
      ;; getter/setter prop
      ((and (= tt js2-NAME)
            (= (js2-peek-token) js2-NAME)
            (or (string= prop "get")
                (string= prop "set")))
-      (js2-consume-token)
+      (js2-get-token)
       (js2-set-face ppos pend 'font-lock-keyword-face 'record)  ; get/set
       (js2-record-face 'font-lock-function-name-face)      ; for peeked name
       (setq name (js2-create-name-node)) ; discard get/set & use peeked name
@@ -9401,7 +9405,7 @@ When `js2-is-in-destructuring' is t, forms like {a, b, c} 
will be permitted."
 PROP is the node representing the property:  a number, name or string."
   (js2-must-match js2-COLON "msg.no.colon.prop")
   (let* ((pos (js2-node-pos prop))
-        (colon (- js2-token-beg pos))
+        (colon (- (js2-current-token-beg) pos))
         (expr (js2-parse-assign-expr))
         (result (make-js2-object-prop-node
                  :pos pos
@@ -9446,19 +9450,16 @@ GET-P is non-nil if the keyword was `get'."
     (js2-node-add-children result prop fn)
     result))
 
-(defun js2-create-name-node (&optional check-activation-p token)
-  "Create a name node using the token info from last scanned name.
-In some cases we need to either synthesize a name node, or we lost
-the name token information by peeking.  If the TOKEN parameter is
-not `js2-NAME', then we use the token info saved in instance vars."
-  (let ((beg js2-token-beg)
-        (s js2-ts-string)
-        name)
-    (when (/= js2-current-token js2-NAME)
-      (setq beg (or js2-prev-name-token-start js2-ts-cursor)
-            s js2-prev-name-token-string
-            js2-prev-name-token-start nil
-            js2-prev-name-token-string nil))
+(defun js2-create-name-node (&optional check-activation-p token string)
+  "Create a name node using the current token and, optionally, STRING.
+And, if CHECK-ACTIVATION-P is non-nil, use the value of TOKEN."
+  (let* ((beg (js2-current-token-beg))
+         (tt (js2-current-token-type))
+         (s (or string
+                (if (= js2-NAME tt)
+                    (js2-current-token-string)
+                  (js2-tt-name tt))))
+         name)
     (setq name (make-js2-name-node :pos beg
                                    :name s
                                    :len (length s)))
diff --git a/tests/parser.el b/tests/parser.el
index 3c05c4b..d7386e8 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -2,6 +2,18 @@
 (require 'ert-x)
 (require 'js2-mode)
 
+(defmacro js2-deftest (name buffer-contents &rest body)
+  `(ert-deftest ,(intern (format "js2-%s" name)) ()
+     (with-temp-buffer
+       (save-excursion
+         (insert ,buffer-contents))
+       (unwind-protect
+           (progn
+             ,@body)
+         (fundamental-mode)))))
+
+(put 'js2-deftest 'lisp-indent-function 'defun)
+
 (defun js2-test-string-to-ast (s)
   (ert-with-test-buffer (:name 'origin)
     (insert s)
@@ -9,11 +21,12 @@
     (should (null js2-mode-buffer-dirty-p))
     js2-mode-ast))
 
-(defun js2-test-parse-string (code-string &key syntax-error)
+(defun* js2-test-parse-string (code-string &key syntax-error errors-count
+                                                reference)
   (let ((ast (js2-test-string-to-ast code-string)))
     (if syntax-error
         (let ((errors (js2-ast-root-errors ast)))
-          (should (= 1 (length errors)))
+          (should (= (or errors-count 1) (length errors)))
           (destructuring-bind (_ pos len) (first errors)
             (should (string= syntax-error (substring code-string
                                                      (1- pos) (+ pos len 
-1))))))
@@ -21,21 +34,44 @@
       (ert-with-test-buffer (:name 'copy)
         (js2-print-tree ast)
         (skip-chars-backward " \t\n")
-        (should (string= code-string (buffer-substring-no-properties
-                                      (point-min) (point))))))))
+        (should (string= (or reference code-string)
+                         (buffer-substring-no-properties
+                          (point-min) (point))))))))
 
-(defmacro* js2-deftest-parse (name code-string &key bind syntax-error)
+(defmacro* js2-deftest-parse (name code-string &key bind syntax-error 
errors-count
+                                                    reference)
   "Parse CODE-STRING.  If SYNTAX-ERROR is nil, print syntax tree
-with `js2-print-tree' and assert the result to be equal to the
-original string.  If SYNTAX-ERROR is passed, expect syntax error
-highlighting substring equal to SYNTAX-ERROR value.
-BIND defines bindings to apply them around the test."
+with `js2-print-tree' and assert the result to be equal to
+REFERENCE, if present, or the original string.  If SYNTAX-ERROR
+is passed, expect syntax error highlighting substring equal to
+SYNTAX-ERROR value.  BIND defines bindings to apply them around
+the test."
   `(ert-deftest ,(intern (format "js2-%s" name)) ()
      (let ,(append bind '((js2-basic-offset 2)))
-       (js2-test-parse-string ,code-string :syntax-error ,syntax-error))))
+       (js2-test-parse-string ,code-string
+                              :syntax-error ,syntax-error
+                              :errors-count ,errors-count
+                              :reference ,reference))))
 
 (put 'js2-deftest-parse 'lisp-indent-function 'defun)
 
+;;; Basics
+
+(js2-deftest-parse variable-assignment
+  "a = 1;")
+
+(js2-deftest-parse empty-object-literal
+  "b = {};")
+
+(js2-deftest-parse empty-array-literal
+  "c = [];")
+
+(js2-deftest-parse comma-after-regexp
+  "d = /eee/, 42;")
+
+(js2-deftest-parse return-statement
+  "function() {\n  return 2;\n}")
+
 ;;; Callers of `js2-valid-prop-name-token'.
 
 (js2-deftest-parse parse-property-access-when-not-keyword
@@ -50,10 +86,10 @@ BIND defines bindings to apply them around the test."
   :bind ((js2-allow-keywords-as-property-names t)
          (js2-compiler-xml-available nil)))
 
-(js2-deftest-parse parse-array-literal-when-not-keyword
+(js2-deftest-parse parse-object-literal-when-not-keyword
   "a = {b: 1};")
 
-(js2-deftest-parse parse-array-literal-when-keyword
+(js2-deftest-parse parse-object-literal-when-keyword
   "a = {in: 1};"
   :bind ((js2-allow-keywords-as-property-names t)))
 
@@ -111,3 +147,58 @@ BIND defines bindings to apply them around the test."
 
 (js2-deftest-parse function-with-rest-after-default-parameter
   "function foo(a = 1, ...rest) {\n}")
+
+;;; Automatic semicolon insertion.
+
+(js2-deftest-parse no-auto-semi-insertion-after-if
+  "if (true) {\n}")
+
+(js2-deftest-parse auto-semi-insertion-after-function
+  "a = function() {}" :reference "a = function() {};")
+
+(js2-deftest-parse auto-semi-one-variable-per-line
+  "x\ny" :reference "x;\ny;")
+
+;;; Labels
+
+(js2-deftest-parse labeled-stmt-node
+  "foo:\nbar:\n  x = y + 1;")
+
+(js2-deftest no-label-node-inside-expr "x = y:"
+  (let (js2-parse-interruptable-p)
+    (js2-mode))
+  (let ((assignment (js2-expr-stmt-node-expr (car (js2-scope-kids 
js2-mode-ast)))))
+    (should (js2-name-node-p (js2-assign-node-right assignment)))))
+
+;;; Tokenizer
+
+(js2-deftest get-token "(1+1)"
+  (js2-init-scanner)
+  (should (eq js2-LP (js2-next-token)))
+  (should (eq js2-NUMBER (js2-next-token)))
+  (should (eq js2-ADD (js2-next-token)))
+  (should (eq js2-NUMBER (js2-next-token)))
+  (should (eq js2-RP (js2-next-token))))
+
+(js2-deftest unget-token "()"
+  (js2-init-scanner)
+  (should (eq js2-LP (js2-next-token)))
+  (js2-unget-token)
+  (should (eq js2-LP (js2-next-token)))
+  (should (eq js2-RP (js2-next-token))))
+
+(js2-deftest get-token-or-eol "x\n++;"
+  (js2-init-scanner)
+  (should (eq js2-NAME (js2-next-token)))
+  (should (eq js2-EOL (js2-peek-token-or-eol)))
+  (should (eq js2-INC (js2-next-token)))
+  (should (eq js2-SEMI (js2-peek-token-or-eol))))
+
+(js2-deftest unget-token-over-eol-and-comment "x\n//abc\ny"
+  (js2-init-scanner)
+  (should (eq js2-NAME (js2-next-token)))
+  (should (eq js2-NAME (js2-next-token)))
+  (should (equal "y" (js2-current-token-string)))
+  (js2-unget-token)
+  (should (eq js2-NAME (js2-current-token-type)))
+  (should (equal "x" (js2-current-token-string))))

commit 1c7f0f3731355fd4002dcb97d1941fe823a5c6ad
Author: Dmitry Gutov <address@hidden>
Date:   Sun Sep 1 00:31:17 2013 +0300

    Remove the "mentioned here" bit, to make text more universal

diff --git a/js2-mode.el b/js2-mode.el
index 6842056..dd53a84 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -80,8 +80,8 @@
 ;; files, and not for editing JavaScript within <script> tags or templates.
 
 ;; The project page on GitHub is used for development and issue tracking.
-;; The original homepage at Google Code is mentioned here for posterity, it has
-;; outdated information and is mostly unmaintained.
+;; The original homepage at Google Code has outdated information and is mostly
+;; unmaintained.
 
 ;;; Code:
 

-----------------------------------------------------------------------

Summary of changes:
 packages/js2-mode/.dir-locals.el   |    1 +
 packages/js2-mode/.elpaignore      |    5 -
 packages/js2-mode/js2-mode.el      | 1653 +++++++++++++++++++-----------------
 packages/js2-mode/tests/externs.el |   19 -
 packages/js2-mode/tests/indent.el  |   26 +-
 packages/js2-mode/tests/parser.el  |  241 +++++-
 6 files changed, 1095 insertions(+), 850 deletions(-)
 create mode 100644 packages/js2-mode/.dir-locals.el
 delete mode 100644 packages/js2-mode/.elpaignore


hooks/post-receive
-- 
ELPA



reply via email to

[Prev in Thread] Current Thread [Next in Thread]