]> code.delx.au - gnu-emacs-elpa/blobdiff - js2-mode.el
js2-parse-let: Simplify
[gnu-emacs-elpa] / js2-mode.el
index 2074173b3047f03e5fab27fa3e3ceda0a7584c62..81060d10ede310da4fe94572228f1957d57a12ca 100644 (file)
@@ -1,12 +1,15 @@
 ;;; js2-mode.el --- Improved JavaScript editing mode
 
-;; Copyright (C) 2009, 2012  Free Software Foundation, Inc.
+;; Copyright (C) 2009, 2011-2014  Free Software Foundation, Inc.
 
 ;; Author: Steve Yegge <steve.yegge@gmail.com>
 ;;         mooz <stillpedant@gmail.com>
 ;;         Dmitry Gutov <dgutov@yandex.ru>
-;; Version: 1.0
+;; URL:  https://github.com/mooz/js2-mode/
+;;       http://code.google.com/p/js2-mode/
+;; Version: 20140114
 ;; Keywords: languages, javascript
+;; Package-Requires: ((emacs "24.1"))
 
 ;; This file is part of GNU Emacs.
 
 ;;    - show some or all block comments as /*...*/
 ;;  - context-sensitive menu bar and popup menus
 ;;  - code browsing using the `imenu' package
-;;  - typing helpers such as automatic insertion of matching braces/parens
 ;;  - many customization options
 
+;; Installation:
+;;
+;; To install it as your major mode for JavaScript editing:
+
+;;   (add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
+
+;; Alternatively, to install it as a minor mode just for JavaScript linting,
+;; you must add it to the appropriate major-mode hook.  Normally this would be:
+
+;;   (add-hook 'js-mode-hook 'js2-minor-mode)
+
+;; You may also want to hook it in for shell scripts running via node.js:
+
+;;   (add-to-list 'interpreter-mode-alist '("node" . js2-mode))
+
 ;; To customize how it works:
 ;;   M-x customize-group RET js2-mode RET
 
 ;; This means that `js2-mode' is currently only useful for editing JavaScript
 ;; 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 has outdated information and is mostly
+;; unmaintained.
+
 ;;; Code:
 
 (eval-when-compile
 
 (eval-and-compile
   (require 'cc-mode)     ; (only) for `c-populate-syntax-table'
-  (require 'cc-langs)    ; it's here in Emacs 21...
   (require 'cc-engine))  ; for `c-paragraph-start' et. al.
 
+(defvar electric-layout-rules)
+
 ;;; Externs (variables presumed to be defined by the host system)
 
 (defvar js2-ecma-262-externs
             DocumentRange Range RangeException
 
             ;; W3C XML
-            XPathResult XMLHttpRequest))
+            XPathResult XMLHttpRequest
+
+            ;; console object.  Provided by at least Chrome and Firefox.
+            console))
   "Browser externs.
 You can cause these to be included or excluded with the custom
 variable `js2-include-browser-externs'.")
@@ -155,20 +180,28 @@ variable `js2-include-browser-externs'.")
 (defvar js2-rhino-externs
   (mapcar 'symbol-name
           '(Packages importClass importPackage com org java
-            ;; Global object (shell) externs
+            ;; Global object (shell) externs.
             defineClass deserialize doctest gc help load
             loadClass print quit readFile readUrl runCommand seal
             serialize spawn sync toint32 version))
   "Mozilla Rhino externs.
 Set `js2-include-rhino-externs' to t to include them.")
 
-(defvar js2-gears-externs
+(defvar js2-node-externs
+  (mapcar 'symbol-name
+          '(__dirname __filename Buffer clearInterval clearTimeout require
+            console exports global module process setInterval setTimeout))
+  "Node.js externs.
+Set `js2-include-node-externs' to t to include them.")
+
+(defvar js2-typed-array-externs
   (mapcar 'symbol-name
-          '(
-            ;; TODO(stevey):  add these
-            ))
-  "Google Gears externs.
-Set `js2-include-gears-externs' to t to include them.")
+          '(ArrayBuffer Uint8ClampedArray DataView
+            Int8Array Uint8Array Int16Array Uint16Array Int32Array Uint32Array
+            Float32Array Float64Array))
+  "Khronos typed array externs. Available in most modern browsers and
+in node.js >= 0.6. If `js2-include-node-externs' or `js2-include-browser-externs'
+are enabled, these will also be included.")
 
 ;;; Variables
 
@@ -178,11 +211,9 @@ variable with predicate PRED."
   (make-variable-buffer-local name)
   (put name 'safe-local-variable pred))
 
-(defvar js2-emacs22 (>= emacs-major-version 22))
-
 (defcustom js2-highlight-level 2
   "Amount of syntax highlighting to perform.
-0 or a negative value means do no highlighting.
+0 or a negative value means none.
 1 adds basic syntax highlighting.
 2 adds highlighting of some Ecma built-in properties.
 3 adds highlighting of many Ecma built-in functions."
@@ -209,19 +240,6 @@ Similar to `c-basic-offset'."
   :type 'integer)
 (js2-mark-safe-local 'js2-basic-offset 'integerp)
 
-;; TODO(stevey):  move this code into a separate minor mode.
-(defcustom js2-mirror-mode nil
-  "Non-nil to insert closing brackets, parens, etc. automatically."
-  :group 'js2-mode
-  :type 'boolean)
-
-(defcustom js2-auto-indent-p nil
-  "Automatic indentation with punctuation characters.
-If non-nil, the current line is indented when certain punctuations
-are inserted."
-  :group 'js2-mode
-  :type 'boolean)
-
 (defcustom js2-bounce-indent-p nil
   "Non-nil to have indent-line function choose among alternatives.
 If nil, the indent-line function will indent to a predetermined column
@@ -253,29 +271,13 @@ lines, it won't be indented additionally:
   :type 'symbol)
 (js2-mark-safe-local 'js2-pretty-multiline-declarations 'symbolp)
 
-(defcustom js2-indent-on-enter-key nil
-  "Non-nil to have Enter/Return key indent the line.
-This is unusual for Emacs modes but common in IDEs like Eclipse."
+(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)
-
-(defcustom js2-enter-indents-newline nil
-  "Non-nil to have Enter/Return key indent the newly-inserted line.
-This is unusual for Emacs modes but common in IDEs like Eclipse."
-  :type 'boolean
-  :group 'js2-mode)
-
-(defcustom js2-rebind-eol-bol-keys t
-  "Non-nil to rebind `beginning-of-line' and `end-of-line' keys.
-If non-nil, bounce between bol/eol and first/last non-whitespace char."
-  :group 'js2-mode
-  :type 'boolean)
-
-(defcustom js2-electric-keys '("{" "}" "(" ")" "[" "]" ":" ";" "," "*")
-  "Keys that auto-indent when `js2-auto-indent-p' is non-nil.
-Each value in the list is passed to `define-key'."
-  :type 'list
-  :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.
@@ -297,14 +299,9 @@ If `js2-dynamic-idle-timer-adjust' is 0 or negative,
   :type 'number
   :group 'js2-mode)
 
-(defcustom js2-mode-escape-quotes t
-  "Non-nil to disable automatic quote-escaping inside strings."
-  :type 'boolean
-  :group 'js2-mode)
-
 (defcustom js2-concat-multiline-strings t
-  "Non-nil to automatically turn a newline in mid-string into a
-string concatenation.  When `eol', the '+' will be inserted at the
+  "When non-nil, `js2-line-break' in mid-string will make it a
+string concatenation. When `eol', the '+' will be inserted at the
 end of the line, otherwise, at the beginning of the next line."
   :type '(choice (const t) (const eol) (const nil))
   :group 'js2-mode)
@@ -323,8 +320,7 @@ even if this flag is non-nil."
 
 (defcustom js2-strict-trailing-comma-warning t
   "Non-nil to warn about trailing commas in array literals.
-Ecma-262 forbids them, but many browsers permit them.  IE is the
-big exception, and can produce bugs if you have trailing commas."
+Ecma-262-5.1 allows them, but older versions of IE raise an error."
   :type 'boolean
   :group 'js2-mode)
 
@@ -392,7 +388,7 @@ Examples:
   var foo = {int: 5, while: 6, continue: 7};
   foo.return = 8;
 
-Ecma-262 forbids this syntax, but many browsers support it."
+Ecma-262 5.1 allows this syntax, but some engines still don't."
   :type 'boolean
   :group 'js2-mode)
 
@@ -402,17 +398,12 @@ This is useful for xulrunner apps."
   :type 'boolean
   :group 'js2-mode)
 
-(defcustom js2-cleanup-whitespace nil
-  "Non-nil to invoke `delete-trailing-whitespace' before saves."
-  :type 'boolean
-  :group 'js2-mode)
-
 (defcustom js2-move-point-on-right-click t
   "Non-nil to move insertion point when you right-click.
 This makes right-click context menu behavior a bit more intuitive,
 since menu operations generally apply to the point.  The exception
 is if there is a region selection, in which case the point does -not-
-move, so cut/copy/paste etc. can work properly.
+move, so cut/copy/paste can work properly.
 
 Note that IntelliJ moves the point, and Eclipse leaves it alone,
 so this behavior is customizable."
@@ -472,10 +463,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.")
 
@@ -665,10 +652,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)
 
@@ -684,26 +672,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.")
 
@@ -711,6 +690,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.")
 
@@ -724,8 +704,30 @@ 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)
+
+(defstruct (js2-ts-state
+            (:constructor make-js2-ts-state (&key (lineno js2-ts-lineno)
+                                                  (cursor js2-ts-cursor)
+                                                  (tokens (copy-sequence js2-ti-tokens))
+                                                  (tokens-cursor js2-ti-tokens-cursor)
+                                                  (lookahead js2-ti-lookahead))))
+  lineno
+  cursor
+  tokens
+  tokens-cursor
+  lookahead)
 
 ;;; Parser variables
 
@@ -767,15 +769,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)
@@ -798,9 +794,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.
 
@@ -814,7 +807,7 @@ Will only be used when we finish implementing the interpreter.")
 
 (defcustom js2-global-externs nil
   "A list of any extern names you'd like to consider always declared.
-This list is global and is used by all js2-mode files.
+This list is global and is used by all `js2-mode' files.
 You can create buffer-local externs list using `js2-additional-externs'.
 
 There is also a buffer-local variable `js2-default-externs',
@@ -831,9 +824,9 @@ These are currently only used for highlighting undeclared variables,
 which only worries about top-level (unqualified) references.
 As js2-mode's processing improves, we will flesh out this list.
 
-The initial value is set to `js2-ecma-262-externs', unless you
-have set `js2-include-browser-externs', in which case the browser
-externs are also included.
+The initial value is set to `js2-ecma-262-externs', unless some
+of the `js2-include-?-externs' variables are set to t, in which
+case the browser, Rhino and/or Node.js externs are also included.
 
 See `js2-additional-externs' for more information.")
 
@@ -841,21 +834,18 @@ See `js2-additional-externs' for more information.")
   "Non-nil to include browser externs in the master externs list.
 If you work on JavaScript files that are not intended for browsers,
 such as Mozilla Rhino server-side JavaScript, set this to nil.
-You can always include them on a per-file basis by calling
-`js2-add-browser-externs' from a function on `js2-mode-hook'.
-
 See `js2-additional-externs' for more information about externs."
   :type 'boolean
   :group 'js2-mode)
 
-(defcustom js2-include-rhino-externs t
+(defcustom js2-include-rhino-externs nil
   "Non-nil to include Mozilla Rhino externs in the master externs list.
 See `js2-additional-externs' for more information about externs."
   :type 'boolean
   :group 'js2-mode)
 
-(defcustom js2-include-gears-externs t
-  "Non-nil to include Google Gears externs in the master externs list.
+(defcustom js2-include-node-externs nil
+  "Non-nil to include Node.js externs in the master externs list.
 See `js2-additional-externs' for more information about externs."
   :type 'boolean
   :group 'js2-mode)
@@ -865,7 +855,7 @@ See `js2-additional-externs' for more information about externs."
 It is used to decide whether variables are considered undeclared
 for purposes of highlighting.
 
-Each entry is a lisp string.  The string should be the fully qualified
+Each entry is a Lisp string.  The string should be the fully qualified
 name of an external entity.  All externs should be added to this list,
 so that as js2-mode's processing improves it can take advantage of them.
 
@@ -874,10 +864,13 @@ First, you can add externs that are valid for all your JavaScript files.
 You should probably do this by adding them to `js2-global-externs', which
 is a global list used for all js2-mode files.
 
-Next, you can add a function to `js2-mode-hook' that adds additional
+Next, you can add a function to `js2-init-hook' that adds additional
 externs appropriate for the specific file, perhaps based on its path.
 These should go in `js2-additional-externs', which is buffer-local.
 
+Third, you can use JSLint's global declaration, as long as
+`js2-include-jslint-globals' is non-nil, which see.
+
 Finally, you can add a function to `js2-post-parse-callbacks',
 which is called after parsing completes, and `js2-mode-ast' is bound to
 the root of the parse tree.  At this stage you can set up an AST
@@ -909,16 +902,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.
@@ -943,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
@@ -1030,7 +1012,7 @@ callback to `js2-mode-wait-for-parse', and your callback will be
 called after the new parse tree is built.  This can take some time
 in large files.")
 
-(defface js2-warning-face
+(defface js2-warning
   `((((class color) (background light))
      (:underline  "orange"))
     (((class color) (background dark))
@@ -1039,7 +1021,7 @@ in large files.")
   "Face for JavaScript warnings."
   :group 'js2-mode)
 
-(defface js2-error-face
+(defface js2-error
   `((((class color) (background light))
      (:foreground "red"))
     (((class color) (background dark))
@@ -1048,115 +1030,109 @@ in large files.")
   "Face for JavaScript errors."
   :group 'js2-mode)
 
-(defface js2-jsdoc-tag-face
+(defface js2-jsdoc-tag
   '((t :foreground "SlateGray"))
   "Face used to highlight @whatever tags in jsdoc comments."
   :group 'js2-mode)
 
-(defface js2-jsdoc-type-face
+(defface js2-jsdoc-type
   '((t :foreground "SteelBlue"))
   "Face used to highlight {FooBar} types in jsdoc comments."
   :group 'js2-mode)
 
-(defface js2-jsdoc-value-face
+(defface js2-jsdoc-value
   '((t :foreground "PeachPuff3"))
   "Face used to highlight tag values in jsdoc comments."
   :group 'js2-mode)
 
-(defface js2-function-param-face
+(defface js2-function-param
   '((t :foreground "SeaGreen"))
   "Face used to highlight function parameters in javascript."
   :group 'js2-mode)
 
-(defface js2-instance-member-face
+(defface js2-function-call
+  '((t :inherit default))
+  "Face used to highlight function name in calls."
+  :group 'js2-mode)
+
+(defface js2-instance-member
   '((t :foreground "DarkOrchid"))
   "Face used to highlight instance variables in javascript.
 Not currently used."
   :group 'js2-mode)
 
-(defface js2-private-member-face
+(defface js2-private-member
   '((t :foreground "PeachPuff3"))
   "Face used to highlight calls to private methods in javascript.
 Not currently used."
   :group 'js2-mode)
 
-(defface js2-private-function-call-face
+(defface js2-private-function-call
   '((t :foreground "goldenrod"))
   "Face used to highlight calls to private functions in javascript.
 Not currently used."
   :group 'js2-mode)
 
-(defface js2-jsdoc-html-tag-name-face
-  (if js2-emacs22
-      '((((class color) (min-colors 88) (background light))
-         (:foreground "rosybrown"))
-        (((class color) (min-colors 8) (background dark))
-         (:foreground "yellow"))
-        (((class color) (min-colors 8) (background light))
-         (:foreground "magenta")))
-    '((((type tty pc) (class color) (background light))
-       (:foreground "magenta"))
-      (((type tty pc) (class color) (background dark))
-       (:foreground "yellow"))
-      (t (:foreground "RosyBrown"))))
+(defface js2-jsdoc-html-tag-name
+  '((((class color) (min-colors 88) (background light))
+     (:foreground "rosybrown"))
+    (((class color) (min-colors 8) (background dark))
+     (:foreground "yellow"))
+    (((class color) (min-colors 8) (background light))
+     (:foreground "magenta")))
     "Face used to highlight jsdoc html tag names"
   :group 'js2-mode)
 
-(defface js2-jsdoc-html-tag-delimiter-face
-  (if js2-emacs22
-      '((((class color) (min-colors 88) (background light))
-         (:foreground "dark khaki"))
-        (((class color) (min-colors 8) (background dark))
-         (:foreground "green"))
-        (((class color) (min-colors 8) (background light))
-         (:foreground "green")))
-    '((((type tty pc) (class color) (background light))
-       (:foreground "green"))
-      (((type tty pc) (class color) (background dark))
-       (:foreground "green"))
-      (t (:foreground "dark khaki"))))
+(defface js2-jsdoc-html-tag-delimiter
+  '((((class color) (min-colors 88) (background light))
+     (:foreground "dark khaki"))
+    (((class color) (min-colors 8) (background dark))
+     (:foreground "green"))
+    (((class color) (min-colors 8) (background light))
+     (:foreground "green")))
   "Face used to highlight brackets in jsdoc html tags."
   :group 'js2-mode)
 
-(defface js2-magic-paren-face
-  '((t :underline t))
-  "Face used to color parens that will be auto-overwritten."
-  :group 'js2-mode)
+(defface js2-external-variable
+  '((t :foreground "orange"))
+  "Face used to highlight undeclared variable identifiers.")
+
+(defcustom js2-init-hook nil
+  "List of functions to be called after `js2-mode' or
+`js2-minor-mode' has initialized all variables, before parsing
+the buffer for the first time."
+  :type 'hook
+  :group 'js2-mode
+  :version "20130608")
 
 (defcustom js2-post-parse-callbacks nil
-  "A list of callback functions invoked after parsing finishes.
+  "List of callback functions invoked after parsing finishes.
 Currently, the main use for this function is to add synthetic
 declarations to `js2-recorded-identifiers', which see."
-  :type 'list
+  :type 'hook
   :group 'js2-mode)
 
-(defface js2-external-variable-face
-  '((t :foreground "orange"))
-  "Face used to highlight undeclared variable identifiers.
+(defcustom js2-highlight-external-variables t
+  "Non-nil to highlight undeclared variable identifiers.
 An undeclared variable is any variable not declared with var or let
 in the current scope or any lexically enclosing scope.  If you use
 such a variable, then you are either expecting it to originate from
 another file, or you've got a potential bug."
-  :group 'js2-mode)
-
-(defcustom js2-highlight-external-variables t
-  "Non-nil to highlight undeclared variable identifiers."
   :type 'boolean
   :group 'js2-mode)
 
-(defcustom js2-auto-insert-catch-block t
-  "Non-nil to insert matching catch block on open-curly after `try'."
+(defcustom js2-include-jslint-globals t
+  "Non-nil to include the identifiers from JSLint global
+declaration (see http://www.jslint.com/lint.html#global) in the
+buffer-local externs list.  See `js2-additional-externs' for more
+information."
   :type 'boolean
   :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 "C-m") #'js2-enter-key)
-    (when js2-rebind-eol-bol-keys
-      (define-key map (kbd "C-a") #'js2-beginning-of-line)
-      (define-key map (kbd "C-e") #'js2-end-of-line))
+    (define-key map (kbd "M-j") #'js2-line-break)
     (define-key map (kbd "C-c C-e") #'js2-mode-hide-element)
     (define-key map (kbd "C-c C-s") #'js2-mode-show-element)
     (define-key map (kbd "C-c C-a") #'js2-mode-show-all)
@@ -1164,18 +1140,7 @@ another file, or you've got a potential bug."
     (define-key map (kbd "C-c C-t") #'js2-mode-toggle-hide-comments)
     (define-key map (kbd "C-c C-o") #'js2-mode-toggle-element)
     (define-key map (kbd "C-c C-w") #'js2-mode-toggle-warnings-and-errors)
-    (define-key map (kbd "C-c C-`") #'js2-next-error)
-    (define-key map (or (car (where-is-internal #'mark-defun))
-                        (kbd "M-C-h"))
-      #'js2-mark-defun)
-    (define-key map (or (car (where-is-internal #'narrow-to-defun))
-                        (kbd "C-x nd"))
-      #'js2-narrow-to-defun)
     (define-key map [down-mouse-3] #'js2-down-mouse-3)
-    (when js2-auto-indent-p
-      (mapc (lambda (key)
-              (define-key map key #'js2-insert-and-indent))
-            js2-electric-keys))
     (when js2-bounce-indent-p
       (define-key map (kbd "<backtab>") #'js2-indent-bounce-backwards))
 
@@ -1194,7 +1159,7 @@ another file, or you've got a potential bug."
       '("--"))
 
     (define-key map [menu-bar javascript next-error]
-      '(menu-item "Next warning or error" js2-next-error
+      '(menu-item "Next warning or error" next-error
                   :enabled (and js2-mode-ast
                                 (or (js2-ast-root-errors js2-mode-ast)
                                     (js2-ast-root-warnings js2-mode-ast)))
@@ -1240,7 +1205,7 @@ another file, or you've got a potential bug."
     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.
@@ -1263,7 +1228,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.
@@ -1291,14 +1256,14 @@ First match-group is the leading whitespace.")
 (defvar js2-mode-verbose-parse-p js2-mode-dev-mode-p
   "Non-nil to emit status messages during parsing.")
 
-(defvar js2-mode-functions-hidden nil "private variable")
-(defvar js2-mode-comments-hidden nil "private variable")
+(defvar js2-mode-functions-hidden nil "Private variable.")
+(defvar js2-mode-comments-hidden nil "Private variable.")
 
 (defvar js2-mode-syntax-table
   (let ((table (make-syntax-table)))
     (c-populate-syntax-table table)
     table)
-  "Syntax table used in js2-mode buffers.")
+  "Syntax table used in `js2-mode' buffers.")
 
 (defvar js2-mode-abbrev-table nil
   "Abbrev table in use in `js2-mode' buffers.")
@@ -1318,11 +1283,11 @@ First match-group is the leading whitespace.")
 
 (defvar js2-message-table
   (make-hash-table :test 'equal :size 250)
-  "Contains localized messages for js2-mode.")
+  "Contains localized messages for `js2-mode'.")
 
 ;; TODO(stevey):  construct this table at compile-time.
 (defmacro js2-msg (key &rest strings)
-  `(puthash ,key (funcall #'concat ,@strings)
+  `(puthash ,key (concat ,@strings)
             js2-message-table))
 
 (defun js2-get-msg (msg-key)
@@ -1542,6 +1507,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.")
 
@@ -1557,6 +1525,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")
 
@@ -1678,7 +1649,7 @@ the correct number of ARGS must be provided."
          "missing ) in parenthetical")
 
 (js2-msg "msg.reserved.id"
-         "identifier is a reserved word")
+         "'%s' is a reserved identifier")
 
 (js2-msg "msg.no.paren.catch"
          "missing ( before catch-block condition")
@@ -1708,10 +1679,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")
@@ -1732,7 +1703,7 @@ the correct number of ARGS must be provided."
          "Code has no side effects")
 
 (js2-msg "msg.extra.trailing.comma"
-         "Trailing comma is not legal in an ECMA-262 object initializer")
+         "Trailing comma is not supported in some browsers")
 
 (js2-msg "msg.array.trailing.comma"
          "Trailing comma yields different behavior across browsers")
@@ -1760,6 +1731,9 @@ the correct number of ARGS must be provided."
 (js2-msg "msg.assn.create.strict"
          "Assignment to undeclared variable %s")
 
+(js2-msg "msg.undeclared.variable"  ; added by js2-mode
+         "Undeclared variable or function '%s'")
+
 (js2-msg "msg.ref.undefined.prop"
          "Reference to undefined property '%s'")
 
@@ -1933,9 +1907,14 @@ the correct number of ARGS must be provided."
          "illegal octal literal digit %s; "
          "interpreting it as a decimal digit")
 
-(js2-msg "msg.reserved.keyword"
-         "illegal usage of future reserved keyword %s; "
-         "interpreting it as ordinary identifier")
+(js2-msg "msg.missing.hex.digits"
+         "missing hexadecimal digits after '0x'")
+
+(js2-msg "msg.missing.binary.digits"
+         "missing binary digits after '0b'")
+
+(js2-msg "msg.missing.octal.digits"
+         "missing octal digits after '0o'")
 
 (js2-msg "msg.script.is.not.constructor"
          "Script objects are not constructors.")
@@ -1970,6 +1949,49 @@ 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))))
+
+(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)
+        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)
@@ -2000,7 +2022,7 @@ Returns nil if element is not found in the list."
     result))
 
 (defmacro js2-time (form)
-  "Evaluate FORM, discard result, and return elapsed time in sec"
+  "Evaluate FORM, discard result, and return elapsed time in sec."
   (declare (debug t))
   (let ((beg (make-symbol "--js2-time-beg--"))
         (delta (make-symbol "--js2-time-end--")))
@@ -2017,12 +2039,6 @@ Returns nil if element is not found in the list."
   (and (>= pos (point-at-bol))
        (<= pos (point-at-eol))))
 
-(defsubst js2-same-line-2 (p1 p2)
-  "Return t if p1 is on the same line as p2."
-  (save-excursion
-    (goto-char p1)
-    (js2-same-line p2)))
-
 (defun js2-code-bug ()
   "Signal an error when we encounter an unexpected code path."
   (error "failed assertion"))
@@ -2033,13 +2049,13 @@ Returns nil if element is not found in the list."
 
 ;; I'd like to associate errors with nodes, but for now the
 ;; easiest thing to do is get the context info from the last token.
-(defsubst js2-record-parse-error (msg &optional arg pos len)
+(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))
 
-(defsubst js2-report-error (msg &optional msg-arg pos len)
+(defun js2-report-error (msg &optional msg-arg pos len)
   "Signal a syntax error or record a parse error."
   (if js2-recover-from-parse-errors
       (js2-record-parse-error msg msg-arg pos len)
@@ -2051,15 +2067,16 @@ Returns nil if element is not found in the list."
                   (current-column))
                 js2-ts-hit-eof))))
 
-(defsubst js2-report-warning (msg &optional msg-arg pos len)
+(defun js2-report-warning (msg &optional msg-arg pos len face)
   (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)))
 
-(defsubst js2-add-strict-warning (msg-id &optional msg-arg beg end)
+(defun js2-add-strict-warning (msg-id &optional msg-arg beg end)
   (if js2-compiler-strict-mode
       (js2-report-warning msg-id msg-arg beg
                           (and beg end (- end beg)))))
@@ -2085,26 +2102,6 @@ Returns nil if element is not found in the list."
 (defsubst js2-flag-not-set-p (flags flag)
   (zerop (logand flags flag)))
 
-;; Stolen shamelessly from James Clark's nxml-mode.
-(defmacro js2-with-unmodifying-text-property-changes (&rest body)
-  "Evaluate BODY without any text property changes modifying the buffer.
-Any text properties changes happen as usual but the changes are not treated as
-modifications to the buffer."
-  (declare (indent 0) (debug t))
-  (let ((modified (make-symbol "modified")))
-    `(let ((,modified (buffer-modified-p))
-           (inhibit-read-only t)
-           (inhibit-modification-hooks t)
-           (buffer-undo-list t)
-           (deactivate-mark nil)
-           ;; Apparently these avoid file locking problems.
-           (buffer-file-name nil)
-           (buffer-file-truename nil))
-       (unwind-protect
-           (progn ,@body)
-         (unless ,modified
-           (restore-buffer-modified-p nil))))))
-
 (defmacro js2-with-underscore-as-word-syntax (&rest body)
   "Evaluate BODY with the _ character set to be word-syntax."
   (declare (indent 0) (debug t))
@@ -2116,34 +2113,24 @@ modifications to the buffer."
            ,@body)
        (modify-syntax-entry ?_ ,old-syntax js2-mode-syntax-table)))))
 
-(defsubst js2-char-uppercase-p (c)
-  "Return t if C is an uppercase character.
-Handles unicode and latin chars properly."
-  (/= c (downcase c)))
-
-(defsubst js2-char-lowercase-p (c)
-  "Return t if C is an uppercase character.
-Handles unicode and latin chars properly."
-  (/= c (upcase c)))
-
 ;;; AST struct and function definitions
 
 ;; flags for ast node property 'member-type (used for e4x operators)
-(defvar js2-property-flag    #x1 "property access: element is valid name")
-(defvar js2-attribute-flag   #x2 "x.@y or x..@y")
-(defvar js2-descendants-flag #x4 "x..y or x..@i")
+(defvar js2-property-flag    #x1 "Property access: element is valid name.")
+(defvar js2-attribute-flag   #x2 "x.@y or x..@y.")
+(defvar js2-descendants-flag #x4 "x..y or x..@i.")
 
 (defsubst js2-relpos (pos anchor)
   "Convert POS to be relative to ANCHOR.
 If POS is nil, returns nil."
   (and pos (- pos anchor)))
 
-(defsubst js2-make-pad (indent)
+(defun js2-make-pad (indent)
   (if (zerop indent)
       ""
     (make-string (* indent js2-basic-offset) ? )))
 
-(defsubst js2-visit-ast (node callback)
+(defun js2-visit-ast (node callback)
   "Visit every node in ast NODE with visitor CALLBACK.
 
 CALLBACK is a function that takes two arguments:  (NODE END-P).  It is
@@ -2156,7 +2143,7 @@ callback is called immediately with a non-nil END-P argument.
 
 The node traversal is approximately lexical-order, although there
 are currently no guarantees around this."
-  (if node
+  (when node
     (let ((vfunc (get (aref node 0) 'js2-visitor)))
       ;; visit the node
       (when  (funcall callback node nil)
@@ -2191,7 +2178,7 @@ are currently no guarantees around this."
   (setf (js2-node-props node)
         (cons (list prop value) (js2-node-props node))))
 
-(defsubst js2-fixup-starts (n nodes)
+(defun js2-fixup-starts (n nodes)
   "Adjust the start positions of NODES to be relative to N.
 Any node in the list may be nil, for convenience."
   (dolist (node nodes)
@@ -2199,7 +2186,7 @@ Any node in the list may be nil, for convenience."
       (setf (js2-node-pos node) (- (js2-node-pos node)
                                    (js2-node-pos n))))))
 
-(defsubst js2-node-add-children (parent &rest nodes)
+(defun js2-node-add-children (parent &rest nodes)
   "Set parent node of NODES to PARENT, and return PARENT.
 Does nothing if we're not recording parent links.
 If any given node in NODES is nil, doesn't record that link."
@@ -2209,7 +2196,7 @@ If any given node in NODES is nil, doesn't record that link."
          (setf (js2-node-parent node) parent))))
 
 ;; Non-recursive since it's called a frightening number of times.
-(defsubst js2-node-abs-pos (n)
+(defun js2-node-abs-pos (n)
   (let ((pos (js2-node-pos n)))
     (while (setq n (js2-node-parent n))
       (setq pos (+ pos (js2-node-pos n))))
@@ -2219,7 +2206,7 @@ If any given node in NODES is nil, doesn't record that link."
   "Return absolute buffer position of end of N."
   (+ (js2-node-abs-pos n) (js2-node-len n)))
 
-;; It's important to make sure block nodes have a lisp list for the
+;; It's important to make sure block nodes have a Lisp list for the
 ;; child nodes, to limit printing recursion depth in an AST that
 ;; otherwise consists of defstruct vectors.  Emacs will crash printing
 ;; a sufficiently large vector tree.
@@ -2228,17 +2215,17 @@ 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)))
   "A block of statements."
-  kids)  ; a lisp list of the child statement nodes
+  kids)  ; a Lisp list of the child statement nodes
 
 (put 'cl-struct-js2-block-node 'js2-visitor 'js2-visit-block)
 (put 'cl-struct-js2-block-node 'js2-printer 'js2-print-block)
 
-(defsubst js2-visit-block (ast callback)
+(defun js2-visit-block (ast callback)
   "Visit the `js2-block-node' children of AST."
   (dolist (kid (js2-block-node-kids ast))
     (js2-visit-ast kid callback)))
@@ -2254,7 +2241,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.
@@ -2270,12 +2257,6 @@ If any given node in NODES is nil, doesn't record that link."
 (put 'cl-struct-js2-scope 'js2-visitor 'js2-visit-block)
 (put 'cl-struct-js2-scope 'js2-printer 'js2-print-none)
 
-(defun js2-scope-set-parent-scope (scope parent)
-  (setf (js2-scope-parent-scope scope) parent
-        (js2-scope-top scope) (if (null parent)
-                                  scope
-                                (js2-scope-top parent))))
-
 (defun js2-node-get-enclosing-scope (node)
   "Return the innermost `js2-scope' node surrounding NODE.
 Returns nil if there is no enclosing scope node."
@@ -2301,7 +2282,7 @@ Returns `js2-scope' in which NAME is defined, or nil if not found."
         (setq scope (js2-scope-parent-scope scope))))
     result))
 
-(defsubst js2-scope-get-symbol (scope name)
+(defun js2-scope-get-symbol (scope name)
   "Return symbol table entry for NAME in SCOPE.
 NAME can be a string or symbol.   Returns a `js2-symbol' or nil if not found."
   (and (js2-scope-symbol-table scope)
@@ -2310,9 +2291,9 @@ NAME can be a string or symbol.   Returns a `js2-symbol' or nil if not found."
                     (intern name))
                   (js2-scope-symbol-table scope)))))
 
-(defsubst js2-scope-put-symbol (scope name symbol)
+(defun js2-scope-put-symbol (scope name symbol)
   "Enter SYMBOL into symbol-table for SCOPE under NAME.
-NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
+NAME can be a Lisp symbol or string.  SYMBOL is a `js2-symbol'."
   (let* ((table (js2-scope-symbol-table scope))
          (sym (if (symbolp name) name (intern name)))
          (entry (assq sym table)))
@@ -2335,7 +2316,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.")
 
@@ -2346,12 +2327,13 @@ 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
+                                                     ;; FIXME: What are those?
                                                      var-decls
                                                      fun-decls)))
-  functions   ; lisp list of nested functions
-  regexps     ; lisp list of (string . flags)
+  functions   ; Lisp list of nested functions
+  regexps     ; Lisp list of (string . flags)
   symbols     ; alist (every symbol gets unique index)
   (param-count 0)
   var-names   ; vector of string names
@@ -2369,14 +2351,14 @@ 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."
   buffer         ; the source buffer from which the code was parsed
-  comments       ; a lisp list of comments, ordered by start position
-  errors         ; a lisp list of errors found during parsing
-  warnings       ; a lisp list of warnings found during parsing
+  comments       ; a Lisp list of comments, ordered by start position
+  errors         ; a Lisp list of errors found during parsing
+  warnings       ; a Lisp list of warnings found during parsing
   node-count)    ; number of nodes in the tree, including the root
 
 (put 'cl-struct-js2-ast-root 'js2-visitor 'js2-visit-ast-root)
@@ -2392,9 +2374,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)
@@ -2417,7 +2399,7 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
   expr)
 
 (defsubst js2-expr-stmt-node-set-has-result (node)
-  "Change the node type to `js2-EXPR_RESULT'.  Used for code generation."
+  "Change NODE type to `js2-EXPR_RESULT'.  Used for code generation."
   (setf (js2-node-type node) js2-EXPR_RESULT))
 
 (put 'cl-struct-js2-expr-stmt-node 'js2-visitor 'js2-visit-expr-stmt-node)
@@ -2442,7 +2424,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
@@ -2473,11 +2455,9 @@ 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)
-                                                    len
-                                                    body
-                                                    condition
-                                                    lp
+                                                    (pos (js2-current-token-beg))
+                                                    len body
+                                                    condition lp
                                                     rp)))
   "AST node for while-loop."
   condition)    ; while-condition
@@ -2502,13 +2482,9 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
             (:constructor nil)
             (:constructor make-js2-for-node (&key (type js2-FOR)
                                                   (pos js2-ts-cursor)
-                                                  len
-                                                  body
-                                                  init
+                                                  len body init
                                                   condition
-                                                  update
-                                                  lp
-                                                  rp)))
+                                                  update lp rp)))
   "AST node for a C-style for-loop."
   init       ; initialization expression
   condition  ; loop condition
@@ -2540,8 +2516,7 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
             (:constructor nil)
             (:constructor make-js2-for-in-node (&key (type js2-FOR)
                                                      (pos js2-ts-cursor)
-                                                     len
-                                                     body
+                                                     len body
                                                      iterator
                                                      object
                                                      in-pos
@@ -2573,9 +2548,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))
@@ -2609,12 +2582,10 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
             (:constructor nil)
             (:constructor make-js2-if-node (&key (type js2-IF)
                                                  (pos js2-ts-cursor)
-                                                 len
-                                                 condition
+                                                 len condition
                                                  then-part
                                                  else-pos
-                                                 else-part
-                                                 lp
+                                                 else-part lp
                                                  rp)))
   "AST node for an if-statement."
   condition   ; expression
@@ -2663,7 +2634,7 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
                                                   finally-block)))
   "AST node for a try-statement."
   try-block
-  catch-clauses  ; a lisp list of `js2-catch-node'
+  catch-clauses  ; a Lisp list of `js2-catch-node'
   finally-block) ; a `js2-finally-node'
 
 (put 'cl-struct-js2-try-node 'js2-visitor 'js2-visit-try-node)
@@ -2698,8 +2669,7 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
                                                     param
                                                     guard-kwd
                                                     guard-expr
-                                                    block
-                                                    lp
+                                                    block lp
                                                     rp)))
   "AST node for a catch clause."
   param       ; destructuring form or simple name node
@@ -2736,8 +2706,7 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
             (:constructor nil)
             (:constructor make-js2-finally-node (&key (type js2-FINALLY)
                                                       (pos js2-ts-cursor)
-                                                      len
-                                                      body)))
+                                                      len body)))
   "AST node for a finally clause."
   body)  ; a `js2-node', often but not always a block node
 
@@ -2760,12 +2729,11 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
                                                      (pos js2-ts-cursor)
                                                      len
                                                      discriminant
-                                                     cases
-                                                     lp
+                                                     cases lp
                                                      rp)))
   "AST node for a switch statement."
   discriminant  ; a `js2-node' (switch expression)
-  cases  ; a lisp list of `js2-case-node'
+  cases  ; a Lisp list of `js2-case-node'
   lp     ; position of open-paren for discriminant, nil if omitted
   rp)    ; position of close-paren for discriminant, nil if omitted
 
@@ -2792,9 +2760,7 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
             (:constructor nil)
             (:constructor make-js2-case-node (&key (type js2-CASE)
                                                    (pos js2-ts-cursor)
-                                                   len
-                                                   kids
-                                                   expr)))
+                                                   len kids expr)))
   "AST node for a case clause of a switch statement."
   expr)   ; the case expression (nil for default)
 
@@ -2822,8 +2788,7 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
             (:constructor nil)
             (:constructor make-js2-throw-node (&key (type js2-THROW)
                                                     (pos js2-ts-cursor)
-                                                    len
-                                                    expr)))
+                                                    len expr)))
   "AST node for a throw statement."
   expr)   ; the expression to throw
 
@@ -2843,11 +2808,8 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
             (:constructor nil)
             (:constructor make-js2-with-node (&key (type js2-WITH)
                                                    (pos js2-ts-cursor)
-                                                   len
-                                                   object
-                                                   body
-                                                   lp
-                                                   rp)))
+                                                   len object
+                                                   body lp rp)))
   "AST node for a with-statement."
   object
   body
@@ -2874,8 +2836,7 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
             (:constructor nil)
             (:constructor make-js2-label-node (&key (type js2-LABEL)
                                                     (pos js2-ts-cursor)
-                                                    len
-                                                    name)))
+                                                    len name)))
   "AST node for a statement label or case label."
   name   ; a string
   loop)  ; for validating and code-generating continue-to-label
@@ -2895,12 +2856,10 @@ NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'."
             ;; no-side-effects warnings, hence js2-EXPR_RESULT.
             (:constructor make-js2-labeled-stmt-node (&key (type js2-EXPR_RESULT)
                                                            (pos js2-ts-cursor)
-                                                           len
-                                                           labels
-                                                           stmt)))
+                                                           len labels stmt)))
   "AST node for a statement with one or more labels.
 Multiple labels for a statement are collapsed into the labels field."
-  labels  ; lisp list of `js2-label-node'
+  labels  ; Lisp list of `js2-label-node'
   stmt)   ; the statement these labels are for
 
 (put 'cl-struct-js2-labeled-stmt-node 'js2-visitor 'js2-visit-labeled-stmt)
@@ -2925,7 +2884,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.
@@ -2948,6 +2907,7 @@ NODE is a `js2-labels-node'.  LABEL is an identifier."
   target) ; target js2-labels-node or loop/switch statement
 
 (defun js2-visit-jump-node (n v)
+  ;; We don't visit the target, since it's a back-link.
   (js2-visit-ast (js2-jump-node-label n) v))
 
 (defstruct (js2-break-node
@@ -2955,9 +2915,7 @@ NODE is a `js2-labels-node'.  LABEL is an identifier."
             (:constructor nil)
             (:constructor make-js2-break-node (&key (type js2-BREAK)
                                                     (pos js2-ts-cursor)
-                                                    len
-                                                    label
-                                                    target)))
+                                                    len label target)))
   "AST node for a break statement.
 The label field is a `js2-name-node', possibly nil, for the named label
 if provided.  E.g. in 'break foo', it represents 'foo'.  The target field
@@ -2978,9 +2936,7 @@ is the target of the break - a label node or enclosing loop/switch statement.")
             (:constructor nil)
             (:constructor make-js2-continue-node (&key (type js2-CONTINUE)
                                                        (pos js2-ts-cursor)
-                                                       len
-                                                       label
-                                                       target)))
+                                                       len label target)))
   "AST node for a continue statement.
 The label field is the user-supplied enclosing label name, a `js2-name-node'.
 It is nil if continue specifies no label.  The target field is the jump target:
@@ -3007,22 +2963,23 @@ 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
+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|ARROW}
   name             ; function name (a `js2-name-node', or nil if anonymous)
-  params           ; a lisp list of destructuring forms or simple name nodes
+  params           ; a Lisp list of destructuring forms or simple name nodes
   rest-p           ; if t, the last parameter is rest parameter
   body             ; a `js2-block-node' or expression node (1.8 only)
   lp               ; position of arg-list open-paren, or nil if omitted
   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, COMPREHENSION or nil
   member-expr)     ; nonstandard Ecma extension from Rhino
 
 (put 'cl-struct-js2-function-node 'js2-visitor 'js2-visit-function-node)
@@ -3035,18 +2992,22 @@ 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))
-        (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"))
+  (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 (eq (js2-function-node-generator-type n) 'STAR)
+        (insert "*")))
     (when name
-        (insert " ")
-        (js2-print-ast name 0))
+      (insert " ")
+      (js2-print-ast name 0))
     (insert "(")
     (loop with len = (length params)
           for param in params
@@ -3057,16 +3018,21 @@ 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"))))
 
-(defsubst js2-function-name (node)
+(defun js2-function-name (node)
   "Return function name for NODE, a `js2-function-node', or nil if anonymous."
   (and (js2-function-node-name node)
        (js2-name-node-name (js2-function-node-name node))))
@@ -3080,15 +3046,14 @@ 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)
-                                                       len
-                                                       kids
+                                                       (pos (js2-current-token-beg))
+                                                       len kids
                                                        decl-type)))
   "AST node for a variable declaration list (VAR, CONST or LET).
 The node bounds differ depending on the declaration type.  For VAR or
 CONST declarations, the bounds include the var/const keyword.  For LET
 declarations, the node begins at the position of the first child."
-  kids        ; a lisp list of `js2-var-init-node' structs.
+  kids        ; a Lisp list of `js2-var-init-node' structs.
   decl-type)  ; js2-VAR, js2-CONST or js2-LET
 
 (put 'cl-struct-js2-var-decl-node 'js2-visitor 'js2-visit-var-decl)
@@ -3104,7 +3069,7 @@ declarations, the node begins at the position of the first child."
     (insert pad)
     (insert (cond
              ((= tt js2-VAR) "var ")
-             ((= tt js2-LET) "")  ; handled by parent let-{expr/stmt}
+             ((= tt js2-LET) "let ")
              ((= tt js2-CONST) "const ")
              (t
               (error "malformed var-decl node"))))
@@ -3122,8 +3087,7 @@ declarations, the node begins at the position of the first child."
             (:constructor nil)
             (:constructor make-js2-var-init-node (&key (type js2-VAR)
                                                        (pos js2-ts-cursor)
-                                                       len
-                                                       target
+                                                       len target
                                                        initializer)))
   "AST node for a variable declaration.
 The type field will be js2-CONST for a const decl."
@@ -3156,8 +3120,7 @@ The type field will be js2-CONST for a const decl."
                                                    test-expr
                                                    true-expr
                                                    false-expr
-                                                   q-pos
-                                                   c-pos)))
+                                                   q-pos c-pos)))
   "AST node for the ternary operator"
   test-expr
   true-expr
@@ -3187,10 +3150,8 @@ The type field will be js2-CONST for a const decl."
             (:constructor nil)
             (:constructor make-js2-infix-node (&key type
                                                     (pos js2-ts-cursor)
-                                                    len
-                                                    op-pos
-                                                    left
-                                                    right)))
+                                                    len op-pos
+                                                    left right)))
   "Represents infix expressions.
 Includes assignment ops like `|=', and the comma operator.
 The type field inherited from `js2-node' holds the operator."
@@ -3239,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 "=")
@@ -3275,10 +3237,8 @@ The type field inherited from `js2-node' holds the operator."
             (:constructor nil)
             (:constructor make-js2-assign-node (&key type
                                                      (pos js2-ts-cursor)
-                                                     len
-                                                     op-pos
-                                                     left
-                                                     right)))
+                                                     len op-pos
+                                                     left right)))
   "Represents any assignment.
 The type field holds the actual assignment operator.")
 
@@ -3290,11 +3250,10 @@ The type field holds the actual assignment operator.")
             (:constructor nil)
             (:constructor make-js2-unary-node (&key type ; required
                                                     (pos js2-ts-cursor)
-                                                    len
-                                                    operand)))
+                                                    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
 
@@ -3324,12 +3283,9 @@ 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)
-                                                  len
-                                                  vars
-                                                  body
-                                                  lp
-                                                  rp)))
+                                                  (pos (js2-current-token-beg))
+                                                  len vars body
+                                                  lp rp)))
   "AST node for a let expression or a let statement.
 Note that a let declaration such as let x=6, y=7 is a `js2-var-decl-node'."
   vars   ; a `js2-var-decl-node'
@@ -3346,7 +3302,9 @@ Note that a let declaration such as let x=6, y=7 is a `js2-var-decl-node'."
 
 (defun js2-print-let-node (n i)
   (insert (js2-make-pad i) "let (")
-  (js2-print-ast (js2-let-node-vars n) 0)
+  (let ((p (point)))
+    (js2-print-ast (js2-let-node-vars n) 0)
+    (delete-region p (+ p 4)))
   (insert ") ")
   (js2-print-ast (js2-let-node-body n) i))
 
@@ -3354,7 +3312,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'.
@@ -3375,23 +3333,20 @@ The node type is set to js2-NULL, js2-THIS, etc.")
              (t (error "Invalid keyword literal type: %d" tt))))))
 
 (defsubst js2-this-node-p (node)
-  "Return t if this node is a `js2-literal-node' of type js2-THIS."
+  "Return t if NODE is a `js2-literal-node' of type js2-THIS."
   (eq (js2-node-type node) js2-THIS))
 
 (defstruct (js2-new-node
             (:include js2-node)
             (:constructor nil)
             (:constructor make-js2-new-node (&key (type js2-NEW)
-                                                  (pos js2-token-beg)
-                                                  len
-                                                  target
-                                                  args
-                                                  initializer
-                                                  lp
-                                                  rp)))
+                                                  (pos (js2-current-token-beg))
+                                                  len target
+                                                  args initializer
+                                                  lp rp)))
   "AST node for new-expression such as new Foo()."
   target  ; an identifier or reference
-  args    ; a lisp list of argument nodes
+  args    ; a Lisp list of argument nodes
   lp      ; position of left-paren, nil if omitted
   rp      ; position of right-paren, nil if omitted
   initializer) ; experimental Rhino syntax:  optional `js2-object-node'
@@ -3419,10 +3374,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)
@@ -3445,11 +3400,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
@@ -3465,11 +3421,10 @@ 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))
-                                                     value
-                                                     flags)))
+                                                             (js2-current-token-beg)))
+                                                     value flags)))
   "AST node for a regular expression literal."
   value  ; the regexp string, without // delimiters
   flags) ; a string of flags, e.g. `mi'.
@@ -3489,10 +3444,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."
@@ -3510,8 +3465,7 @@ You can tell the quote type by looking at the first character."
             (:constructor nil)
             (:constructor make-js2-array-node (&key (type js2-ARRAYLIT)
                                                     (pos js2-ts-cursor)
-                                                    len
-                                                    elems)))
+                                                    len elems)))
   "AST node for an array literal."
   elems)  ; list of expressions.  [foo,,bar] yields a nil middle element.
 
@@ -3520,11 +3474,14 @@ You can tell the quote type by looking at the first character."
 
 (defun js2-visit-array-node (n v)
   (dolist (e (js2-array-node-elems n))
-    (js2-visit-ast e v)))
+    (js2-visit-ast e v)))  ; Can be nil; e.g. [a, ,b].
 
 (defun js2-print-array-node (n i)
   (insert (js2-make-pad i) "[")
-  (js2-print-list (js2-array-node-elems n))
+  (let ((elems (js2-array-node-elems n)))
+    (js2-print-list elems)
+    (when (and elems (null (car (last elems))))
+      (insert ",")))
   (insert "]"))
 
 (defstruct (js2-object-node
@@ -3535,8 +3492,8 @@ You can tell the quote type by looking at the first character."
                                                      len
                                                      elems)))
   "AST node for an object literal expression.
-`elems' is a list of either `js2-object-prop-node' or `js2-name-node',
-the latter represents abbreviation in destructuring expressions."
+`elems' is a list of either `js2-object-prop-node' or `js2-name-node'.
+The latter represents abbreviation in destructuring expressions."
   elems)
 
 (put 'cl-struct-js2-object-node 'js2-visitor 'js2-visit-object-node)
@@ -3556,10 +3513,8 @@ the latter represents abbreviation in destructuring expressions."
             (:constructor nil)
             (:constructor make-js2-object-prop-node (&key (type js2-COLON)
                                                           (pos js2-ts-cursor)
-                                                          len
-                                                          left
-                                                          right
-                                                          op-pos)))
+                                                          len left
+                                                          right op-pos)))
   "AST node for an object literal prop:value entry.
 The `left' field is the property:  a name node, string node or number node.
 The `right' field is a `js2-node' representing the initializer value.")
@@ -3578,9 +3533,7 @@ The `right' field is a `js2-node' representing the initializer value.")
             (:constructor nil)
             (:constructor make-js2-getter-setter-node (&key type ; GET or SET
                                                             (pos js2-ts-cursor)
-                                                            len
-                                                            left
-                                                            right)))
+                                                            len left right)))
   "AST node for a getter/setter property in an object literal.
 The `left' field is the `js2-name-node' naming the getter/setter prop.
 The `right' field is always an anonymous `js2-function-node' with a node
@@ -3603,9 +3556,7 @@ property `GETTER_SETTER' set to js2-GET or js2-SET. ")
             (:constructor nil)
             (:constructor make-js2-prop-get-node (&key (type js2-GETPROP)
                                                        (pos js2-ts-cursor)
-                                                       len
-                                                       left
-                                                       right)))
+                                                       len left right)))
   "AST node for a dotted property reference, e.g. foo.bar or foo().bar")
 
 (put 'cl-struct-js2-prop-get-node 'js2-visitor 'js2-visit-prop-get-node)
@@ -3626,11 +3577,8 @@ property `GETTER_SETTER' set to js2-GET or js2-SET. ")
             (:constructor nil)
             (:constructor make-js2-elem-get-node (&key (type js2-GETELEM)
                                                        (pos js2-ts-cursor)
-                                                       len
-                                                       target
-                                                       element
-                                                       lb
-                                                       rb)))
+                                                       len target element
+                                                       lb rb)))
   "AST node for an array index expression such as foo[bar]."
   target  ; a `js2-node' - the expression preceding the "."
   element ; a `js2-node' - the expression in brackets
@@ -3656,14 +3604,11 @@ property `GETTER_SETTER' set to js2-GET or js2-SET. ")
             (:constructor nil)
             (:constructor make-js2-call-node (&key (type js2-CALL)
                                                    (pos js2-ts-cursor)
-                                                   len
-                                                   target
-                                                   args
-                                                   lp
-                                                   rp)))
+                                                   len target args
+                                                   lp rp)))
   "AST node for a JavaScript function call."
   target  ; a `js2-node' evaluating to the function to call
-  args  ; a lisp list of `js2-node' arguments
+  args  ; a Lisp list of `js2-node' arguments
   lp    ; position of open-paren, or nil if missing
   rp)   ; position of close-paren, or nil if missing
 
@@ -3687,9 +3632,9 @@ property `GETTER_SETTER' set to js2-GET or js2-SET. ")
             (:constructor nil)
             (:constructor make-js2-yield-node (&key (type js2-YIELD)
                                                     (pos js2-ts-cursor)
-                                                    len
-                                                    value)))
+                                                    len value star-p)))
   "AST node for yield statement or expression."
+  star-p ; whether it's yield*
   value) ; optional:  value to be yielded
 
 (put 'cl-struct-js2-yield-node 'js2-visitor 'js2-visit-yield-node)
@@ -3701,6 +3646,8 @@ property `GETTER_SETTER' set to js2-GET or js2-SET. ")
 (defun js2-print-yield-node (n i)
   (insert (js2-make-pad i))
   (insert "yield")
+  (when (js2-yield-node-star-p n)
+    (insert "*"))
   (when (js2-yield-node-value n)
     (insert " ")
     (js2-print-ast (js2-yield-node-value n) 0)))
@@ -3710,8 +3657,7 @@ property `GETTER_SETTER' set to js2-GET or js2-SET. ")
             (:constructor nil)
             (:constructor make-js2-paren-node (&key (type js2-LP)
                                                     (pos js2-ts-cursor)
-                                                    len
-                                                    expr)))
+                                                    len expr)))
   "AST node for a parenthesized expression.
 In particular, used when the parens are syntactically optional,
 as opposed to required parens such as those enclosing an if-conditional."
@@ -3729,87 +3675,95 @@ as opposed to required parens such as those enclosing an if-conditional."
   (js2-print-ast (js2-paren-node-expr n) 0)
   (insert ")"))
 
-(defstruct (js2-array-comp-node
+(defstruct (js2-comp-node
             (:include js2-scope)
             (:constructor nil)
-            (:constructor make-js2-array-comp-node (&key (type js2-ARRAYCOMP)
-                                                         (pos js2-ts-cursor)
-                                                         len
-                                                         result
-                                                         loops
-                                                         filter
-                                                         if-pos
-                                                         lp
-                                                         rp)))
+            (:constructor make-js2-comp-node (&key (type js2-ARRAYCOMP)
+                                                   (pos js2-ts-cursor)
+                                                   len result
+                                                   loops filters
+                                                   form)))
   "AST node for an Array comprehension such as [[x,y] for (x in foo) for (y in bar)]."
   result  ; result expression (just after left-bracket)
-  loops   ; a lisp list of `js2-array-comp-loop-node'
-  filter  ; guard/filter expression
-  if-pos  ; buffer pos of 'if' keyword, if present, else nil
-  lp      ; buffer position of if-guard left-paren, or nil if not present
-  rp)     ; buffer position of if-guard right-paren, or nil if not present
-
-(put 'cl-struct-js2-array-comp-node 'js2-visitor 'js2-visit-array-comp-node)
-(put 'cl-struct-js2-array-comp-node 'js2-printer 'js2-print-array-comp-node)
-
-(defun js2-visit-array-comp-node (n v)
-  (js2-visit-ast (js2-array-comp-node-result n) v)
-  (dolist (l (js2-array-comp-node-loops n))
+  loops   ; a Lisp list of `js2-comp-loop-node'
+  filters ; a Lisp list of guard/filter expressions
+  form    ; ARRAY, LEGACY_ARRAY or STAR_GENERATOR
+          ; SpiderMonkey also supports "legacy generator expressions", but we dont.
+  )
+
+(put 'cl-struct-js2-comp-node 'js2-visitor 'js2-visit-comp-node)
+(put 'cl-struct-js2-comp-node 'js2-printer 'js2-print-comp-node)
+
+(defun js2-visit-comp-node (n v)
+  (js2-visit-ast (js2-comp-node-result n) v)
+  (dolist (l (js2-comp-node-loops n))
     (js2-visit-ast l v))
-  (js2-visit-ast (js2-array-comp-node-filter n) v))
+  (dolist (f (js2-comp-node-filters n))
+    (js2-visit-ast f v)))
 
-(defun js2-print-array-comp-node (n i)
+(defun js2-print-comp-node (n i)
   (let ((pad (js2-make-pad i))
-        (result (js2-array-comp-node-result n))
-        (loops (js2-array-comp-node-loops n))
-        (filter (js2-array-comp-node-filter n)))
-    (insert pad "[")
-    (js2-print-ast result 0)
+        (result (js2-comp-node-result n))
+        (loops (js2-comp-node-loops n))
+        (filters (js2-comp-node-filters n))
+        (legacy-p (eq (js2-comp-node-form n) 'LEGACY_ARRAY))
+        (gen-p (eq (js2-comp-node-form n) 'STAR_GENERATOR)))
+    (insert pad (if gen-p "(" "["))
+    (when legacy-p
+      (js2-print-ast result 0))
     (dolist (l loops)
-      (insert " ")
-      (js2-print-ast l 0))
-    (when filter
-      (insert " if (")
-      (js2-print-ast filter 0)
-      (insert ")"))
-    (insert "]")))
-
-(defstruct (js2-array-comp-loop-node
+      (when legacy-p
+        (insert " "))
+      (js2-print-ast l 0)
+      (unless legacy-p
+        (insert " ")))
+    (dolist (f filters)
+      (when legacy-p
+        (insert " "))
+      (insert "if (")
+      (js2-print-ast f 0)
+      (insert ")")
+      (unless legacy-p
+        (insert " ")))
+    (unless legacy-p
+      (js2-print-ast result 0))
+    (insert (if gen-p ")" "]"))))
+
+(defstruct (js2-comp-loop-node
             (:include js2-for-in-node)
             (:constructor nil)
-            (:constructor make-js2-array-comp-loop-node (&key (type js2-FOR)
-                                                              (pos js2-ts-cursor)
-                                                              len
-                                                              iterator
-                                                              object
-                                                              in-pos
-                                                              foreach-p
-                                                              each-pos
-                                                              forof-p
-                                                              lp rp)))
+            (:constructor make-js2-comp-loop-node (&key (type js2-FOR)
+                                                        (pos js2-ts-cursor)
+                                                        len iterator
+                                                        object in-pos
+                                                        foreach-p
+                                                        each-pos
+                                                        forof-p
+                                                        lp rp)))
   "AST subtree for each 'for (foo in bar)' loop in an array comprehension.")
 
-(put 'cl-struct-js2-array-comp-loop-node 'js2-visitor 'js2-visit-array-comp-loop)
-(put 'cl-struct-js2-array-comp-loop-node 'js2-printer 'js2-print-array-comp-loop)
+(put 'cl-struct-js2-comp-loop-node 'js2-visitor 'js2-visit-comp-loop)
+(put 'cl-struct-js2-comp-loop-node 'js2-printer 'js2-print-comp-loop)
 
-(defun js2-visit-array-comp-loop (n v)
-  (js2-visit-ast (js2-array-comp-loop-node-iterator n) v)
-  (js2-visit-ast (js2-array-comp-loop-node-object n) v))
+(defun js2-visit-comp-loop (n v)
+  (js2-visit-ast (js2-comp-loop-node-iterator n) v)
+  (js2-visit-ast (js2-comp-loop-node-object n) v))
 
-(defun js2-print-array-comp-loop (n i)
-  (insert "for (")
-  (js2-print-ast (js2-array-comp-loop-node-iterator n) 0)
-  (if (js2-array-comp-loop-node-forof-p n)
-      (insert " of ")
-    (insert " in "))
-  (js2-print-ast (js2-array-comp-loop-node-object n) 0)
+(defun js2-print-comp-loop (n _i)
+  (insert "for ")
+  (when (js2-comp-loop-node-foreach-p n) (insert "each "))
+  (insert "(")
+  (js2-print-ast (js2-comp-loop-node-iterator n) 0)
+  (insert (if (js2-comp-loop-node-forof-p n)
+              " of " " in "))
+  (js2-print-ast (js2-comp-loop-node-object n) 0)
   (insert ")"))
 
 (defstruct (js2-empty-expr-node
             (: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.")
 
@@ -3820,9 +3774,8 @@ 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)
-                                                  len
-                                                  kids)))
+                                                  (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
 a `js2-xml-js-expr-node'.  Equivalent to Rhino's XmlLiteral node.")
@@ -3839,8 +3792,7 @@ a `js2-xml-js-expr-node'.  Equivalent to Rhino's XmlLiteral node.")
             (:constructor nil)
             (:constructor make-js2-xml-js-expr-node (&key (type js2-XML)
                                                           (pos js2-ts-cursor)
-                                                          len
-                                                          expr)))
+                                                          len expr)))
   "AST node for an embedded JavaScript {expression} in an E4X literal.
 The start and end fields correspond to the curly-braces."
   expr)  ; a `js2-expr-node' of some sort
@@ -3862,11 +3814,8 @@ The start and end fields correspond to the curly-braces."
             (:constructor nil)
             (:constructor make-js2-xml-dot-query-node (&key (type js2-DOTQUERY)
                                                             (pos js2-ts-cursor)
-                                                            op-pos
-                                                            len
-                                                            left
-                                                            right
-                                                            rp)))
+                                                            op-pos len left
+                                                            right rp)))
   "AST node for an E4X foo.(bar) filter expression.
 Note that the left-paren is automatically the character immediately
 following the dot (.) in the operator.  No whitespace is permitted
@@ -3921,11 +3870,9 @@ 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)
-                                                           len
-                                                           propname
-                                                           namespace
-                                                           at-pos
+                                                           (pos (js2-current-token-beg))
+                                                           len propname
+                                                           namespace at-pos
                                                            colon-pos)))
   "AST node for an E4X XML [expr] property-ref expression.
 The JavaScript syntax is an optional @, an optional ns::, and a name.
@@ -3962,13 +3909,9 @@ expression."
             (:include js2-xml-ref-node)
             (:constructor nil)
             (:constructor make-js2-xml-elem-ref-node (&key (type js2-REF_MEMBER)
-                                                           (pos js2-token-beg)
-                                                           len
-                                                           expr
-                                                           lb
-                                                           rb
-                                                           namespace
-                                                           at-pos
+                                                           (pos (js2-current-token-beg))
+                                                           len expr lb rb
+                                                           namespace at-pos
                                                            colon-pos)))
   "AST node for an E4X XML [expr] member-ref expression.
 Syntax:
@@ -4017,15 +3960,12 @@ end of the index expression."
             (:constructor nil)
             (:constructor make-js2-xml-start-tag-node (&key (type js2-XML)
                                                             (pos js2-ts-cursor)
-                                                            len
-                                                            name
-                                                            attrs
-                                                            kids
+                                                            len name attrs kids
                                                             empty-p)))
   "AST node for an XML start-tag.  Not currently used.
-The `kids' field is a lisp list of child content nodes."
+The `kids' field is a Lisp list of child content nodes."
   name      ; a `js2-xml-name-node'
-  attrs     ; a lisp list of `js2-xml-attr-node'
+  attrs     ; a Lisp list of `js2-xml-attr-node'
   empty-p)  ; t if this is an empty element such as <foo bar="baz"/>
 
 (put 'cl-struct-js2-xml-start-tag-node 'js2-visitor 'js2-visit-xml-start-tag)
@@ -4052,8 +3992,7 @@ The `kids' field is a lisp list of child content nodes."
             (:constructor nil)
             (:constructor make-js2-xml-end-tag-node (&key (type js2-XML)
                                                           (pos js2-ts-cursor)
-                                                          len
-                                                          name)))
+                                                          len name)))
   "AST node for an XML end-tag.  Not currently used."
   name)  ; a `js2-xml-name-node'
 
@@ -4074,9 +4013,7 @@ The `kids' field is a lisp list of child content nodes."
             (:constructor nil)
             (:constructor make-js2-xml-name-node (&key (type js2-XML)
                                                        (pos js2-ts-cursor)
-                                                       len
-                                                       namespace
-                                                       kids)))
+                                                       len namespace kids)))
   "AST node for an E4X XML name.  Not currently used.
 Any XML name can be qualified with a namespace, hence the namespace field.
 Further, any E4X name can be comprised of arbitrary JavaScript {} expressions.
@@ -4103,9 +4040,7 @@ For a simple name, the kids list has exactly one node, a `js2-name-node'."
             (:constructor nil)
             (:constructor make-js2-xml-pi-node (&key (type js2-XML)
                                                      (pos js2-ts-cursor)
-                                                     len
-                                                     name
-                                                     attrs)))
+                                                     len name attrs)))
   "AST node for an E4X XML processing instruction.  Not currently used."
   name   ; a `js2-xml-name-node'
   attrs) ; a list of `js2-xml-attr-node'
@@ -4131,8 +4066,7 @@ For a simple name, the kids list has exactly one node, a `js2-name-node'."
             (:constructor nil)
             (:constructor make-js2-xml-cdata-node (&key (type js2-XML)
                                                         (pos js2-ts-cursor)
-                                                        len
-                                                        content)))
+                                                        len content)))
   "AST node for a CDATA escape section.  Not currently used."
   content)  ; a `js2-string-node' with node-property 'quote-type 'cdata
 
@@ -4151,11 +4085,8 @@ For a simple name, the kids list has exactly one node, a `js2-name-node'."
             (:constructor nil)
             (:constructor make-js2-attr-node (&key (type js2-XML)
                                                    (pos js2-ts-cursor)
-                                                   len
-                                                   name
-                                                   value
-                                                   eq-pos
-                                                   quote-type)))
+                                                   len name value
+                                                   eq-pos quote-type)))
   "AST node representing a foo='bar' XML attribute value.  Not yet used."
   name   ; a `js2-xml-name-node'
   value  ; a `js2-xml-name-node'
@@ -4184,10 +4115,9 @@ For a simple name, the kids list has exactly one node, a `js2-name-node'."
             (:constructor nil)
             (:constructor make-js2-text-node (&key (type js2-XML)
                                                    (pos js2-ts-cursor)
-                                                   len
-                                                   content)))
+                                                   len content)))
   "AST node for an E4X XML text node.  Not currently used."
-  content)  ; a lisp list of `js2-string-node' and `js2-xml-js-expr-node'
+  content)  ; a Lisp list of `js2-string-node' and `js2-xml-js-expr-node'
 
 (put 'cl-struct-js2-xml-text-node 'js2-visitor 'js2-visit-xml-text-node)
 (put 'cl-struct-js2-xml-text-node 'js2-printer 'js2-print-xml-text-node)
@@ -4238,25 +4168,13 @@ If N has no parent pointer, returns N."
         (js2-node-root parent)
       n)))
 
-(defun js2-node-position-in-parent (node &optional parent)
-  "Return the position of NODE in parent's block-kids list.
-PARENT can be supplied if known.  Positioned returned is zero-indexed.
-Returns 0 if NODE is not a child of a block statement, or if NODE
-is not a statement node."
-  (let ((p (or parent (js2-node-parent node)))
-        (i 0))
-    (if (not (js2-block-node-p p))
-        i
-      (or (js2-position node (js2-block-node-kids p))
-          0))))
-
 (defsubst js2-node-short-name (n)
   "Return the short name of node N as a string, e.g. `js2-if-node'."
   (substring (symbol-name (aref n 0))
              (length "cl-struct-")))
 
-(defsubst js2-node-child-list (node)
-  "Return the child list for NODE, a lisp list of nodes.
+(defun js2-node-child-list (node)
+  "Return the child list for NODE, a Lisp list of nodes.
 Works for block nodes, array nodes, obj literals, funarg lists,
 var decls and try nodes (for catch clauses).  Note that you should call
 `js2-block-node-kids' on the function body for the body statements.
@@ -4281,7 +4199,7 @@ Returns nil for zero-length child lists or unsupported nodes."
    (t
     nil)))
 
-(defsubst js2-node-set-child-list (node kids)
+(defun js2-node-set-child-list (node kids)
   "Set the child list for NODE to KIDS."
    (cond
     ((js2-function-node-p node)
@@ -4306,8 +4224,8 @@ Returns nil for zero-length child lists or unsupported nodes."
 
 ;; All because Common Lisp doesn't support multiple inheritance for defstructs.
 (defconst js2-paren-expr-nodes
-  '(cl-struct-js2-array-comp-loop-node
-    cl-struct-js2-array-comp-node
+  '(cl-struct-js2-comp-loop-node
+    cl-struct-js2-comp-node
     cl-struct-js2-call-node
     cl-struct-js2-catch-node
     cl-struct-js2-do-node
@@ -4334,7 +4252,7 @@ a `js2-new-node' and there are no arguments or parentheses."
   (memq (aref node 0) js2-paren-expr-nodes))
 
 ;; Fake polymorphism... yech.
-(defsubst js2-node-lp (node)
+(defun js2-node-lp (node)
   "Return relative left-paren position for NODE, if applicable.
 For `js2-elem-get-node' structs, returns left-bracket position.
 Note that the position may be nil in the case of a parse error."
@@ -4352,15 +4270,15 @@ Note that the position may be nil in the case of a parse error."
    ((js2-call-node-p node)
     (js2-call-node-lp node))
    ((js2-paren-node-p node)
-    (js2-node-pos node))
+    0)
    ((js2-switch-node-p node)
     (js2-switch-node-lp node))
    ((js2-catch-node-p node)
     (js2-catch-node-lp node))
    ((js2-let-node-p node)
     (js2-let-node-lp node))
-   ((js2-array-comp-node-p node)
-    (js2-array-comp-node-lp node))
+   ((js2-comp-node-p node)
+    0)
    ((js2-with-node-p node)
     (js2-with-node-lp node))
    ((js2-xml-dot-query-node-p node)
@@ -4369,7 +4287,7 @@ Note that the position may be nil in the case of a parse error."
     (error "Unsupported node type: %s" (js2-node-short-name node)))))
 
 ;; Fake polymorphism... blech.
-(defsubst js2-node-rp (node)
+(defun js2-node-rp (node)
   "Return relative right-paren position for NODE, if applicable.
 For `js2-elem-get-node' structs, returns right-bracket position.
 Note that the position may be nil in the case of a parse error."
@@ -4387,15 +4305,15 @@ Note that the position may be nil in the case of a parse error."
    ((js2-call-node-p node)
     (js2-call-node-rp node))
    ((js2-paren-node-p node)
-    (+ (js2-node-pos node) (js2-node-len node)))
+    (1- (js2-node-len node)))
    ((js2-switch-node-p node)
     (js2-switch-node-rp node))
    ((js2-catch-node-p node)
     (js2-catch-node-rp node))
    ((js2-let-node-p node)
     (js2-let-node-rp node))
-   ((js2-array-comp-node-p node)
-    (js2-array-comp-node-rp node))
+   ((js2-comp-node-p node)
+    (1- (js2-node-len node)))
    ((js2-with-node-p node)
     (js2-with-node-rp node))
    ((js2-xml-dot-query-node-p node)
@@ -4404,11 +4322,11 @@ Note that the position may be nil in the case of a parse error."
     (error "Unsupported node type: %s" (js2-node-short-name node)))))
 
 (defsubst js2-node-first-child (node)
-  "Returns the first element of `js2-node-child-list' for NODE."
+  "Return the first element of `js2-node-child-list' for NODE."
   (car (js2-node-child-list node)))
 
 (defsubst js2-node-last-child (node)
-  "Returns the last element of `js2-node-last-child' for NODE."
+  "Return the last element of `js2-node-last-child' for NODE."
   (car (last (js2-node-child-list node))))
 
 (defun js2-node-prev-sibling (node)
@@ -4445,20 +4363,18 @@ 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)))
-        kid
-        result
-        fn
+        (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 '>= '<))
     (while (and kids continue)
       (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))
 
@@ -4489,7 +4405,7 @@ node, or if parent links were not recorded during parsing."
          (js2-ast-root-p root)
          (js2-ast-root-buffer root))))
 
-(defsubst js2-block-node-push (n kid)
+(defun js2-block-node-push (n kid)
   "Push js2-node KID onto the end of js2-block-node N's child list.
 KID is always added to the -end- of the kids list.
 Function also calls `js2-node-add-children' to add the parent link."
@@ -4500,13 +4416,10 @@ Function also calls `js2-node-add-children' to add the parent link."
     (js2-node-add-children n kid)))
 
 (defun js2-node-string (node)
-  (let ((buf (js2-node-buffer node))
-        pos)
-    (unless buf
-      (error "No buffer available for node %s" node))
-    (with-current-buffer buf
-      (buffer-substring-no-properties (setq pos (js2-node-abs-pos node))
-                                      (+ pos (js2-node-len node))))))
+  (with-current-buffer (or (js2-node-buffer node)
+                           (error "No buffer available for node %s" node))
+    (let ((pos (js2-node-abs-pos node)))
+      (buffer-substring-no-properties pos (+ pos (js2-node-len node))))))
 
 ;; Container for storing the node we're looking for in a traversal.
 (js2-deflocal js2-discovered-node nil)
@@ -4604,8 +4517,7 @@ POS is a buffer position that defaults to current point.
 Function returns nil if POS was not in any comment node."
   (let ((ast js2-mode-ast)
         (x (or pos (point)))
-        beg
-        end)
+        beg end)
     (unless ast
       (error "No JavaScript AST available"))
     (catch 'done
@@ -4660,33 +4572,7 @@ If NODE is the ast-root, returns nil."
       (setq node (js2-node-parent node)))
     node))
 
-(defsubst js2-nested-function-p (node)
-  "Return t if NODE is a nested function, or is inside a nested function."
-  (unless (js2-ast-root-p node)
-    (js2-function-node-p (if (js2-function-node-p node)
-                             (js2-node-parent-script-or-fn node)
-                           (js2-node-parent-script-or-fn
-                            (js2-node-parent-script-or-fn node))))))
-
-(defsubst js2-function-param-node-p (node)
-  "Return non-nil if NODE is a param node of a `js2-function-node'."
-  (let ((parent (js2-node-parent node)))
-    (and parent
-         (js2-function-node-p parent)
-         (memq node (js2-function-node-params parent)))))
-
-(defsubst js2-mode-shift-kids (kids start offset)
-  (dolist (kid kids)
-    (if (> (js2-node-pos kid) start)
-        (incf (js2-node-pos kid) offset))))
-
-(defsubst js2-mode-shift-children (parent start offset)
-  "Update start-positions of all children of PARENT beyond START."
-  (let ((root (js2-node-root parent)))
-    (js2-mode-shift-kids (js2-node-child-list parent) start offset)
-    (js2-mode-shift-kids (js2-ast-root-comments root) start offset)))
-
-(defsubst js2-node-is-descendant (node ancestor)
+(defun js2-node-is-descendant (node ancestor)
   "Return t if NODE is a descendant of ANCESTOR."
   (while (and node
               (not (eq node ancestor)))
@@ -4695,11 +4581,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)
@@ -4714,7 +4600,7 @@ If NODE is the ast-root, returns nil."
         for arg in args
         for count from 1
         do
-        (js2-print-ast arg 0)
+        (when arg (js2-print-ast arg 0))
         (if (< count len)
             (insert (or delimiter ", ")))))
 
@@ -4729,8 +4615,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))))
@@ -4816,14 +4701,14 @@ You should use `js2-print-tree' instead of this function."
     (let ((tt (js2-node-type node)))
       (cond
        ;; This doubtless needs some work, since EXPR_VOID is used
-       ;; in several ways in Rhino, and I may not have caught them all.
+       ;; in several ways in Rhino and I may not have caught them all.
        ;; I'll wait for people to notice incorrect warnings.
        ((and (= tt js2-EXPR_VOID)
              (js2-expr-stmt-node-p node)) ; but not if EXPR_RESULT
         (let ((expr (js2-expr-stmt-node-expr node)))
           (or (js2-node-has-side-effects expr)
               (when (js2-string-node-p expr)
-                (string= "use strict" (js2-string-node-value expr))))))
+                (member (js2-string-node-value expr) '("use strict" "use asm"))))))
        ((= tt js2-COMMA)
         (js2-node-has-side-effects (js2-infix-node-right node)))
        ((or (= tt js2-AND)
@@ -4840,28 +4725,6 @@ You should use `js2-print-tree' instead of this function."
        (t
         (aref js2-side-effecting-tokens tt))))))
 
-(defun js2-member-expr-leftmost-name (node)
-  "For an expr such as foo.bar.baz, return leftmost node foo.
-NODE is any `js2-node' object.  If it represents a member expression,
-which is any sequence of property gets, element-gets, function calls,
-or xml descendants/filter operators, then we look at the lexically
-leftmost (first) node in the chain.  If it is a name-node we return it.
-Note that NODE can be a raw name-node and it will be returned as well.
-If NODE is not a name-node or member expression, or if it is a member
-expression whose leftmost target is not a name node, returns nil."
-  (let ((continue t)
-        result)
-    (while (and continue (not result))
-      (cond
-       ((js2-name-node-p node)
-        (setq result node))
-       ((js2-prop-get-node-p node)
-        (setq node (js2-prop-get-node-left node)))
-       ;; TODO:  handle call-nodes, xml-nodes, others?
-       (t
-        (setq continue nil))))
-    result))
-
 (defconst js2-stmt-node-types
   (list js2-BLOCK
         js2-BREAK
@@ -4897,7 +4760,7 @@ For these node types in a statement context, the parent will be a
 Functions aren't included in the check."
   (memq (js2-node-type node) js2-stmt-node-types))
 
-(defsubst js2-mode-find-first-stmt (node)
+(defun js2-mode-find-first-stmt (node)
   "Search upward starting from NODE looking for a statement.
 For purposes of this function, a `js2-function-node' counts."
   (while (not (or (js2-stmt-node-p node)
@@ -4984,7 +4847,7 @@ Returns t if the function satisfies strict mode requirement."
                                       js2-END_YIELDS)))))
 
 (defun js2-end-check-if (node)
-  "Returns in the then and else blocks must be consistent with each other.
+  "Ensure that return usage in then/else blocks is consistent.
 If there is no else block, then the return statement can fall through.
 Returns logical OR of END_* flags"
   (let ((th (js2-if-node-then-part node))
@@ -5043,11 +4906,11 @@ Returns logical OR of END_* flags."
    rv))
 
 (defun js2-end-check-loop (node)
-  "Return statement in the loop body must be consistent. The default
-assumption for any kind of a loop is that it will eventually terminate.
-The only exception is a loop with a constant true condition. Code that
-follows such a loop is examined only if one can statically determine
-that there is a break out of the loop.
+  "Return statement in the loop body must be consistent.
+The default assumption for any kind of a loop is that it will eventually
+terminate.  The only exception is a loop with a constant true condition.
+Code that follows such a loop is examined only if one can determine
+statically that there is a break out of the loop.
 
     for(... ; ... ; ...) {}
     for(... in ... ) {}
@@ -5184,7 +5047,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
@@ -5197,7 +5060,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))
@@ -5211,9 +5074,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)))
@@ -5224,27 +5087,25 @@ 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
 
-(defsubst js2-report-scan-error (msg &optional no-throw beg len)
-  (setq js2-token-end js2-ts-cursor)
+(defun js2-report-scan-error (msg &optional no-throw beg len)
+  (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)))
 
-(defsubst js2-get-string-from-buffer ()
-  "Reverse the char accumulator and return it as a string."
-  (setq js2-token-end 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.
@@ -5260,7 +5121,7 @@ Signals an error if it's not a recognized token."
 
 ;; Rhino distinguishes \r and \n line endings.  We don't need to
 ;; because we only scan from Emacs buffers, which always use \n.
-(defsubst js2-get-char ()
+(defun js2-get-char ()
   "Read and return the next character from the input buffer.
 Increments `js2-ts-lineno' if the return value is a newline char.
 Updates `js2-ts-cursor' to the point after the returned char.
@@ -5281,7 +5142,7 @@ Also updates `js2-ts-hit-eof' and `js2-ts-line-start' as needed."
       ;; TODO:  skip over format characters
       c)))
 
-(defsubst js2-read-unicode-escape ()
+(defun js2-read-unicode-escape ()
   "Read a \\uNNNN sequence from the input.
 Assumes the ?\ and ?u have already been read.
 Returns the unicode character, or nil if it wasn't a valid character.
@@ -5294,10 +5155,10 @@ 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 "[0-9a-fA-F]\\{4\\}" s)
           (read (concat "?\\u" s))))))
 
-(defsubst js2-match-char (test)
+(defun js2-match-char (test)
   "Consume and return next character if it matches TEST, a character.
 Returns nil and consumes nothing if TEST is not the next character."
   (let ((c (js2-get-char)))
@@ -5306,27 +5167,36 @@ Returns nil and consumes nothing if TEST is not the next character."
       (js2-unget-char)
       nil)))
 
-(defsubst js2-peek-char ()
+(defun js2-peek-char ()
   (prog1
       (js2-get-char)
     (js2-unget-char)))
 
-(defsubst js2-java-identifier-start-p (c)
+(defun js2-identifier-start-p (c)
+  "Is C a valid start to an ES5 Identifier?
+See http://es5.github.io/#x7.6"
   (or
    (memq c '(?$ ?_))
-   (js2-char-uppercase-p c)
-   (js2-char-lowercase-p c)))
+   (memq (get-char-code-property c 'general-category)
+         ;; Letters
+         '(Lu Ll Lt Lm Lo Nl))))
 
-(defsubst js2-java-identifier-part-p (c)
-  "Implementation of java.lang.Character.isJavaIdentifierPart()"
-  ;; TODO:  make me Unicode-friendly.  See comments above.
+(defun js2-identifier-part-p (c)
+  "Is C a valid part of an ES5 Identifier?
+See http://es5.github.io/#x7.6"
   (or
-   (memq c '(?$ ?_))
-   (js2-char-uppercase-p c)
-   (js2-char-lowercase-p c)
-   (and (>= c ?0) (<= c ?9))))
-
-(defsubst js2-alpha-p (c)
+   (memq c '(?$ ?_ ?\u200c  ?\u200d))
+   (memq (get-char-code-property c 'general-category)
+         '(;; Letters
+           Lu Ll Lt Lm Lo Nl
+           ;; Combining Marks
+           Mn Mc
+           ;; Digits
+           Nd
+           ;; Connector Punctuation
+           Pc))))
+
+(defun js2-alpha-p (c)
   (cond ((and (<= ?A c) (<= c ?Z)) t)
         ((and (<= ?a c) (<= c ?z)) t)
         (t nil)))
@@ -5334,7 +5204,7 @@ Returns nil and consumes nothing if TEST is not the next character."
 (defsubst js2-digit-p (c)
   (and (<= ?0 c) (<= c ?9)))
 
-(defsubst js2-js-space-p (c)
+(defun js2-js-space-p (c)
   (if (<= c 127)
       (memq c '(#x20 #x9 #xB #xC #xD))
     (or
@@ -5344,33 +5214,31 @@ Returns nil and consumes nothing if TEST is not the next character."
 
 (defconst js2-eol-chars (list js2-EOF_CHAR ?\n ?\r))
 
-(defsubst 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)))
+(defun js2-skip-line ()
+  "Skip to end of line."
+  (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.
-BUF defaults to current-buffer and line defaults to 1.
+BUF defaults to `current-buffer' and LINE defaults to 1.
 
 A buffer can only have one scanner active at a time, which yields
 dramatically simpler code than using a defstruct.  If you need to
 have simultaneous scanners in a buffer, copy the regions to scan
 into temp buffers."
-  (save-excursion
-    (when buf
-      (set-buffer buf))
+  (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
@@ -5383,12 +5251,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)))))
 
@@ -5441,21 +5309,10 @@ into temp buffers."
   "Vector whose values are non-nil for tokens that are keywords.
 The values are default faces to use for highlighting the keywords.")
 
-(defconst js2-reserved-words
-  '(abstract
-    boolean byte
-    char class
-    double
-    enum export extends
-    final float
-    goto
-    implements import int interface
-    long
-    native
-    package private protected public
-    short static super synchronized
-    throws transient
-    volatile))
+;; FIXME: Support strict mode-only future reserved words, after we know
+;; which parts scopes are in strict mode, and which are not.
+(defconst js2-reserved-words '(class export extends import super)
+  "Future reserved keywords in ECMAScript 5.")
 
 (defconst js2-keyword-names
   (let ((table (make-hash-table :test 'equal)))
@@ -5476,18 +5333,12 @@ The values are default faces to use for highlighting the keywords.")
     table)
   "JavaScript reserved words by name, mapped to 'js2-RESERVED.")
 
-(defsubst js2-collect-string (buf)
+(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.
@@ -5495,18 +5346,19 @@ 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))
 
-(defsubst js2-x-digit-to-int (c accumulator)
+(defun js2-x-digit-to-int (c accumulator)
   "Build up a hex number.
 If C is a hexadecimal digit, return ACCUMULATOR * 16 plus
 corresponding number.  Otherwise return -1."
@@ -5530,22 +5382,72 @@ corresponding number.  Otherwise return -1."
     (logior c (lsh accumulator 4))))
 
 (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
-        is-integer
-        quote-char
-        val
-        look-for-slash
-        continue)
+  "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 str result base
+        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.
@@ -5554,10 +5456,11 @@ corresponding number.  Otherwise return -1."
           (setq c (js2-get-char))
           (cond
            ((eq c js2-EOF_CHAR)
-            (js2-ts-set-char-token-bounds)
+            (js2-unget-char)
+            (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))
@@ -5565,7 +5468,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?
@@ -5581,7 +5484,7 @@ corresponding number.  Otherwise return -1."
             (js2-unget-char)
             (setq c ?\\)))
          (t
-          (when (setq identifier-start (js2-java-identifier-start-p c))
+          (when (setq identifier-start (js2-identifier-start-p c))
             (setq js2-ts-string-buffer nil)
             (js2-add-to-string c))))
         (when identifier-start
@@ -5597,7 +5500,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
@@ -5617,11 +5520,15 @@ corresponding number.  Otherwise return -1."
                     (js2-report-scan-error "msg.illegal.character" t)))
                  (t
                   (if (or (eq c js2-EOF_CHAR)
-                          (not (js2-java-identifier-part-p c)))
+                          (not (js2-identifier-part-p c)))
                       (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)
+          ;; FIXME: Invalid in ES5 and ES6, see
+          ;; https://bugzilla.mozilla.org/show_bug.cgi?id=694360
+          ;; Probably should just drop this conditional.
           (unless contains-escape
             ;; OPT we shouldn't have to make a string (object!) to
             ;; check if it's a keyword.
@@ -5631,11 +5538,11 @@ corresponding number.  Otherwise return -1."
                        (memq result '(js2-LET js2-YIELD)))
                   ;; 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)))
-              (js2-report-warning "msg.reserved.keyword" str)))
+              (when (eq result 'js2-RESERVED)
+                (setf (js2-token-string token) str))
+              (throw 'return (js2-tt-code result))))
           ;; 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)
@@ -5648,28 +5555,52 @@ corresponding number.  Otherwise return -1."
              ((or (eq c ?x) (eq c ?X))
               (setq base 16)
               (setq c (js2-get-char)))
+             ((and (or (eq c ?b) (eq c ?B))
+                   (>= js2-language-version 200))
+              (setq base 2)
+              (setq c (js2-get-char)))
+             ((and (or (eq c ?o) (eq c ?O))
+                   (>= js2-language-version 200))
+              (setq base 8)
+              (setq c (js2-get-char)))
              ((js2-digit-p c)
-              (setq base 8))
+              (setq base 'maybe-8))
              (t
               (js2-add-to-string ?0))))
-          (if (eq base 16)
+          (cond
+           ((eq base 16)
+            (if (> 0 (js2-x-digit-to-int c 0))
+                (js2-report-scan-error "msg.missing.hex.digits")
               (while (<= 0 (js2-x-digit-to-int c 0))
                 (js2-add-to-string c)
-                (setq c (js2-get-char)))
+                (setq c (js2-get-char)))))
+           ((eq base 2)
+            (if (not (memq c '(?0 ?1)))
+                (js2-report-scan-error "msg.missing.binary.digits")
+              (while (memq c '(?0 ?1))
+                (js2-add-to-string c)
+                (setq c (js2-get-char)))))
+           ((eq base 8)
+            (if (or (> ?0 c) (< ?7 c))
+                (js2-report-scan-error "msg.missing.octal.digits")
+              (while (and (<= ?0 c) (>= ?7 c))
+                (js2-add-to-string c)
+                (setq c (js2-get-char)))))
+           (t
             (while (and (<= ?0 c) (<= c ?9))
               ;; We permit 08 and 09 as decimal numbers, which
               ;; makes our behavior a superset of the ECMA
               ;; numeric grammar.  We might not always be so
               ;; permissive, so we warn about it.
-              (when (and (eq base 8) (>= c ?8))
+              (when (and (eq base 'maybe-8) (>= c ?8))
                 (js2-report-warning "msg.bad.octal.literal"
                                     (if (eq c ?8) "8" "9"))
                 (setq base 10))
               (js2-add-to-string c)
-              (setq c (js2-get-char))))
-          (setq is-integer t)
+              (setq c (js2-get-char)))
+            (when (eq base 'maybe-8)
+              (setq base 8))))
           (when (and (eq base 10) (memq c '(?. ?e ?E)))
-            (setq is-integer nil)
             (when (eq c ?.)
               (loop do
                     (js2-add-to-string c)
@@ -5688,13 +5619,9 @@ 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-set-string-from-buffer token)))
+            (setf (js2-token-number token)
+                  (js2-string-to-number str base)))
           (throw 'return js2-NUMBER))
         ;; is it a string?
         (when (memq c '(?\" ?\'))
@@ -5710,7 +5637,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 ?\\)
@@ -5731,7 +5658,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
@@ -5742,7 +5669,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)
@@ -5793,9 +5720,10 @@ 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))
+          (js2-set-string-from-buffer token)
           (throw 'return js2-STRING))
-        (case c
+        (js2-ts-return token
+         (case c
           (?\;
            (throw 'return js2-SEMI))
           (?\[
@@ -5816,43 +5744,44 @@ 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 ?>)
+                 (js2-ts-return token js2-ARROW)
+               (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
@@ -5860,46 +5789,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-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?
            (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)
@@ -5909,38 +5838,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-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-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
@@ -5952,30 +5881,36 @@ 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))
+           c)
           (otherwise
            (js2-report-scan-error "msg.illegal.character")))))))
+   (setf (js2-token-type token) tt)
+   token))
+
+(defsubst js2-string-to-number (str base)
+  ;; TODO:  Maybe port ScriptRuntime.stringToNumber.
+  (condition-case nil
+      (string-to-number str base)
+    (overflow-error -1)))
 
-(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
+  (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)) ?/)
@@ -5983,9 +5918,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 ?\\)
@@ -6010,11 +5945,11 @@ 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)
+      (js2-set-string-from-buffer token)
       ;; 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
@@ -6023,16 +5958,16 @@ corresponding number.  Otherwise return -1."
   (js2-unget-char)
   (js2-get-next-xml-token))
 
-(defsubst 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
@@ -6055,11 +5990,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))
+                   (js2-set-string-from-buffer token)
                    (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)
@@ -6071,7 +6006,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))
+                  (js2-set-string-from-buffer token)
                   (throw 'return js2-XMLEND)))
                (t
                 ;; else not tag content
@@ -6091,9 +6026,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 [
@@ -6111,28 +6046,28 @@ 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
-                      ;; ex) let xml = <![CDATA[ foo bar baz ]]>;
+                      ;; Allow bare CDATA section, e.g.:
+                      ;;   let xml = <![CDATA[ foo bar baz ]]>;
                       (when (zerop js2-ts-xml-open-tags-count)
                         (throw 'return js2-XMLEND)))
                      (??
                       (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))
@@ -6142,24 +6077,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))
+                   (js2-set-string-from-buffer token)
                    (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)
@@ -6175,10 +6111,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)
@@ -6194,10 +6130,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
@@ -6210,10 +6146,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
@@ -6223,16 +6159,12 @@ 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)))
 
-(defun js2-scanner-get-line ()
-  "Return the text of the current scan line."
-  (buffer-substring (point-at-bol) (point-at-eol)))
-
 ;;; Highlighting
 
-(defsubst js2-set-face (beg end face &optional record)
+(defun js2-set-face (beg end face &optional record)
   "Fontify a region.  If RECORD is non-nil, record for later."
   (when (plusp js2-highlight-level)
     (setq beg (min (point-max) beg)
@@ -6243,19 +6175,6 @@ corresponding number.  Otherwise return -1."
         (push (list beg end face) js2-mode-fontifications)
       (put-text-property beg end 'font-lock-face face))))
 
-(defsubst js2-set-kid-face (pos kid len face)
-  "Set-face on a child node.
-POS is absolute buffer position of parent.
-KID is the child node.
-LEN is the length to fontify.
-FACE is the face to fontify with."
-  (js2-set-face (+ pos (js2-node-pos kid))
-                (+ pos (js2-node-pos kid) (js2-node-len kid))
-                face))
-
-(defsubst js2-fontify-kwd (start length)
-  (js2-set-face start (+ start length) 'font-lock-keyword-face))
-
 (defsubst js2-clear-face (beg end)
   (remove-text-properties beg end '(font-lock-face nil
                                     help-echo nil
@@ -6363,51 +6282,50 @@ Shown at or above `js2-highlight-level' 2.")
   "Built-in functions defined by Ecma-262 and SpiderMonkey extensions.
 Shown at or above `js2-highlight-level' 3.")
 
-(defsubst js2-parse-highlight-prop-get (parent target prop call-p)
+(defun js2-parse-highlight-prop-get (parent target prop call-p)
   (let ((target-name (and target
                           (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.
@@ -6442,15 +6360,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) {...}.
@@ -6476,7 +6391,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.")
 
@@ -6577,27 +6492,27 @@ 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.")
 
-(defsubst js2-jsdoc-highlight-helper ()
+(defun js2-jsdoc-highlight-helper ()
   (js2-set-face (match-beginning 1)
                 (match-end 1)
-                'js2-jsdoc-tag-face)
+                'js2-jsdoc-tag)
   (if (match-beginning 2)
       (if (save-excursion
             (goto-char (match-beginning 2))
             (= (char-after) ?{))
           (js2-set-face (1+ (match-beginning 2))
                         (1- (match-end 2))
-                        'js2-jsdoc-type-face)
+                        'js2-jsdoc-type)
         (js2-set-face (match-beginning 2)
                       (match-end 2)
-                      'js2-jsdoc-value-face)))
+                      'js2-jsdoc-value)))
   (if (match-beginning 3)
       (js2-set-face (match-beginning 3)
                     (match-end 3)
-                    'js2-jsdoc-value-face)))
+                    'js2-jsdoc-value)))
 
 (defun js2-highlight-jsdoc (ast)
   "Highlight doc comment tags."
@@ -6624,17 +6539,17 @@ of a simple name.  Called before EXPR has a parent node."
             (while (re-search-forward js2-jsdoc-html-tag-regexp nil t)
               (js2-set-face (match-beginning 1)
                             (match-end 1)
-                            'js2-jsdoc-html-tag-delimiter-face)
+                            'js2-jsdoc-html-tag-delimiter)
               (js2-set-face (match-beginning 2)
                             (match-end 2)
-                            'js2-jsdoc-html-tag-name-face)
+                            'js2-jsdoc-html-tag-name)
               (js2-set-face (match-beginning 3)
                             (match-end 3)
-                            'js2-jsdoc-html-tag-delimiter-face))))))))
+                            '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
@@ -6654,10 +6569,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 ()
@@ -6675,11 +6590,46 @@ it is considered declared."
                     (member name js2-default-externs)
                     (member name js2-additional-externs)
                     (js2-get-defining-scope scope name))
-          (js2-set-face pos end 'js2-external-variable-face 'record)
-          (js2-record-text-property pos end 'help-echo "Undeclared variable")
-          (js2-record-text-property pos end 'point-entered #'js2-echo-help))))
+          (js2-report-warning "msg.undeclared.variable" name pos (- end pos)
+                              'js2-external-variable))))
     (setq js2-recorded-identifiers nil)))
 
+(defun js2-set-default-externs ()
+  "Set the value of `js2-default-externs' based on the various
+`js2-include-?-externs' variables."
+  (setq js2-default-externs
+        (append js2-ecma-262-externs
+                (if js2-include-browser-externs js2-browser-externs)
+                (if js2-include-rhino-externs js2-rhino-externs)
+                (if js2-include-node-externs js2-node-externs)
+                (if (or js2-include-browser-externs js2-include-node-externs)
+                    js2-typed-array-externs))))
+
+(defun js2-apply-jslint-globals ()
+  (setq js2-additional-externs
+        (nconc (js2-get-jslint-globals)
+               js2-additional-externs)))
+
+(defun js2-get-jslint-globals ()
+  (loop for node in (js2-ast-root-comments js2-mode-ast)
+        when (and (eq 'block (js2-comment-node-format node))
+                  (save-excursion
+                    (goto-char (js2-node-abs-pos node))
+                    (looking-at "/\\*global ")))
+        append (js2-get-jslint-globals-in
+                (match-end 0)
+                (js2-node-abs-end node))))
+
+(defun js2-get-jslint-globals-in (beg end)
+  (let (res)
+    (save-excursion
+      (goto-char beg)
+      (while (re-search-forward js2-mode-identifier-re end t)
+        (let ((match (match-string 0)))
+          (unless (member match '("true" "false"))
+            (push match res)))))
+    (nreverse res)))
+
 ;;; IMenu support
 
 ;; We currently only support imenu, but eventually should support speedbar and
@@ -6756,7 +6706,7 @@ it is considered declared."
 ;; that isn't a valid JavaScript identifier, because you might make foo
 ;; a function and then start setting properties on it that are also functions.
 
-(defsubst js2-prop-node-name (node)
+(defun js2-prop-node-name (node)
   "Return the name of a node that may be a property-get/property-name.
 If NODE is not a valid name-node, string-node or integral number-node,
 returns nil.  Otherwise returns the string name/value of the node."
@@ -6771,7 +6721,7 @@ returns nil.  Otherwise returns the string name/value of the node."
    ((js2-this-node-p node)
     "this")))
 
-(defsubst js2-node-qname-component (node)
+(defun js2-node-qname-component (node)
   "Return the name of this node, if it contributes to a qname.
 Returns nil if the node doesn't contribute."
   (copy-sequence
@@ -6780,7 +6730,7 @@ Returns nil if the node doesn't contribute."
                 (js2-function-node-name node))
            (js2-name-node-name (js2-function-node-name node))))))
 
-(defsubst js2-record-imenu-entry (fn-node qname pos)
+(defun js2-record-imenu-entry (fn-node qname pos)
   "Add an entry to `js2-imenu-recorder'.
 FN-NODE should be the current item's function node.
 
@@ -6799,7 +6749,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
@@ -6853,7 +6803,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.
@@ -6872,7 +6822,7 @@ we append the property name to QNAME, then call `js2-record-imenu-entry'."
                                      (append qname (list (js2-infix-node-left e)))
                                      (+ pos (js2-node-pos right)))))))))
 
-(defsubst js2-node-top-level-decl-p (node)
+(defun js2-node-top-level-decl-p (node)
   "Return t if NODE's name is defined in the top-level scope.
 Also returns t if NODE's name is not defined in any scope, since it implies
 that it's an external variable, which must also be in the top-level scope."
@@ -6888,8 +6838,8 @@ that it's an external variable, which must also be in the top-level scope."
       (js2-ast-root-p defining-scope))
      (t t))))
 
-(defsubst js2-wrapper-function-p (node)
-  "Returns t if NODE is a function expression that's immediately invoked.
+(defun js2-wrapper-function-p (node)
+  "Return t if NODE is a function expression that's immediately invoked.
 NODE must be `js2-function-node'."
   (let ((parent (js2-node-parent node)))
     (or
@@ -6908,7 +6858,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
@@ -6972,7 +6922,7 @@ For instance, processing a nested scope requires a parent function node."
 ;; The sub-alist entries immediately follow INDEX-NAME, the head of the list.
 
 (defun js2-treeify (lst)
-  "Convert (a b c d) to (a ((b ((c d)))))"
+  "Convert (a b c d) to (a ((b ((c d)))))."
   (if (null (cddr lst))  ; list length <= 2
       lst
     (list (car lst) (list (js2-treeify (cdr lst))))))
@@ -7075,9 +7025,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-token-beg js2-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.
@@ -7086,160 +7037,116 @@ is only true until the node is added to its parent; i.e., while parsing."
   (+ (js2-node-pos n)
      (js2-node-len n)))
 
-(defsubst js2-record-comment ()
+(defun js2-record-comment (token)
   "Record a comment in `js2-scanned-comments'."
-  (push (make-js2-comment-node :len (- js2-token-end js2-token-beg)
-                               :format js2-ts-comment-type)
-        js2-scanned-comments)
-  (when js2-parse-ide-mode
-    (js2-record-face (if (eq js2-ts-comment-type 'jsdoc)
-                         'font-lock-doc-face
-                       'font-lock-comment-face))
-    (when (memq js2-ts-comment-type '(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))))
-
-;; 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.
-(defsubst js2-peek-token ()
-  "Returns 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-face))
-         ((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
-
-(defsubst js2-peek-flagged-token ()
-  "Returns 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))
-
-(defsubst js2-next-token ()
-  (prog1
-      (js2-peek-token)
-    (js2-consume-token)))
+  (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.
+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)))
 
-(defsubst js2-next-flagged-token ()
-  (js2-peek-token)
-  (prog1 js2-current-flagged-token
-    (js2-consume-token)))
+(defalias 'js2-next-token 'js2-get-token)
 
-(defsubst js2-match-token (match)
-  "Consume and return t if next token matches MATCH, a bytecode.
+(defun js2-match-token (match &optional dont-unget)
+  "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 (unless dont-unget (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))
 
-(defsubst js2-valid-prop-name-token (tt)
+(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)
+           (or (= tt js2-RESERVED)
+               (aref js2-kwd-tokens tt)))))
 
-(defsubst js2-match-prop-name ()
+(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))
 
-(defsubst js2-must-match-prop-name (msg-id &optional pos len)
+(defun js2-must-match-prop-name (msg-id &optional pos len)
   (if (js2-match-prop-name)
       t
     (js2-report-error msg-id nil pos len)
     nil))
 
-(defsubst 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
+(defun js2-peek-token-or-eol ()
+  "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)))
 
-(defsubst 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))
-
-(defsubst js2-must-match (token msg-id &optional pos len)
+(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.
 Returns t on match, nil if no match."
-  (if (js2-match-token token)
+  (if (js2-match-token token t)
       t
     (js2-report-error msg-id nil pos len)
+    (js2-unget-token)
+    nil))
+
+(defun js2-must-match-name (msg-id)
+  (if (js2-match-token js2-NAME t)
+      t
+    (if (eq (js2-current-token-type) js2-RESERVED)
+        (js2-report-error "msg.reserved.id" (js2-current-token-string))
+      (js2-report-error msg-id)
+      (js2-unget-token))
     nil))
 
 (defsubst js2-inside-function ()
   (plusp js2-nesting-of-function))
 
-(defsubst js2-set-requires-activation ()
+(defun js2-set-requires-activation ()
   (if (js2-function-node-p js2-current-script-or-fn)
       (setf (js2-function-node-needs-activation js2-current-script-or-fn) t)))
 
-(defsubst 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)
@@ -7247,15 +7154,17 @@ Returns t on match, nil if no match."
                  (gethash name js2-compiler-activation-names)))
         (js2-set-requires-activation))))
 
-(defsubst 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)))
+(defun js2-set-is-generator ()
+  (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))))
 
-(defsubst js2-must-have-xml ()
+(defun js2-must-have-xml ()
   (unless js2-compiler-xml-available
     (js2-report-error "msg.XML.not.available")))
 
-(defsubst js2-push-scope (scope)
+(defun js2-push-scope (scope)
   "Push SCOPE, a `js2-scope', onto the lexical scope chain."
   (assert (js2-scope-p scope))
   (assert (null (js2-scope-parent-scope scope)))
@@ -7267,7 +7176,7 @@ Returns t on match, nil if no match."
   (setq js2-current-scope
         (js2-scope-parent-scope js2-current-scope)))
 
-(defsubst js2-enter-loop (loop-node)
+(defun js2-enter-loop (loop-node)
   (push loop-node js2-loop-set)
   (push loop-node js2-loop-and-switch-set)
   (js2-push-scope loop-node)
@@ -7280,7 +7189,7 @@ Returns t on match, nil if no match."
           (js2-label-node-loop (car (js2-labeled-stmt-node-labels
                                      js2-labeled-stmt))) loop-node)))
 
-(defsubst js2-exit-loop ()
+(defun js2-exit-loop ()
   (pop js2-loop-set)
   (pop js2-loop-and-switch-set)
   (js2-pop-scope))
@@ -7292,7 +7201,7 @@ Returns t on match, nil if no match."
   (pop js2-loop-and-switch-set))
 
 (defun js2-parse (&optional buf cb)
-  "Tells the js2 parser to parse a region of JavaScript.
+  "Tell the js2 parser to parse a region of JavaScript.
 
 BUF is a buffer or buffer name containing the code to parse.
 Call `narrow-to-region' first to parse only part of the buffer.
@@ -7326,9 +7235,7 @@ leaving a statement, an expression, or a function definition."
         (max-specpdl-size (max max-specpdl-size 3000))
         (case-fold-search nil)
         ast)
-    (message nil)  ; clear any error message from previous parse
-    (save-excursion
-      (when buf (set-buffer buf))
+    (with-current-buffer (or buf (current-buffer))
       (setq js2-scanned-comments nil
             js2-parsed-errors nil
             js2-parsed-warnings nil
@@ -7336,7 +7243,7 @@ leaving a statement, an expression, or a function definition."
             js2-imenu-function-map nil
             js2-label-set nil)
       (js2-init-scanner)
-      (setq ast (js2-with-unmodifying-text-property-changes
+      (setq ast (with-silent-modifications
                   (js2-do-parse)))
       (unless js2-ts-hit-eof
         (js2-report-error "msg.got.syntax.errors" (length js2-parsed-errors)))
@@ -7360,18 +7267,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))))
+            (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)))
       ;; add function or statement to script
       (setq end (js2-node-end n))
@@ -7387,15 +7293,11 @@ Scanner should be initialized."
     (setq js2-mode-ast root)  ; Make sure this is available for callbacks.
     ;; Give extensions a chance to muck with things before highlighting starts.
     (let ((js2-additional-externs js2-additional-externs))
-      (dolist (callback js2-post-parse-callbacks)
-        (funcall callback))
+      (save-excursion
+        (run-hooks 'js2-post-parse-callbacks))
       (js2-highlight-undeclared-vars))
     root))
 
-(defun js2-function-parser ()
-  (js2-consume-token)
-  (js2-parse-function 'FUNCTION_EXPRESSION_STATEMENT))
-
 (defun js2-parse-function-closure-body (fn-node)
   "Parse a JavaScript 1.8 function closure body."
   (let ((js2-nesting-of-function (1+ js2-nesting-of-function)))
@@ -7411,7 +7313,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)
@@ -7422,12 +7324,12 @@ Scanner should be initialized."
                         (= tt js2-RC)))
           (js2-block-node-push pn (if (/= tt js2-FUNCTION)
                                       (js2-parse-statement)
-                                    (js2-consume-token)
-                                    (js2-parse-function 'FUNCTION_STATEMENT))))
+                                    (js2-get-token)
+                                    (js2-parse-function-stmt))))
       (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)
@@ -7461,33 +7363,40 @@ 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-token-beg pos))
-    (let (params len 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 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"))
               (setq param (js2-parse-destruct-primary-expr))
               (js2-define-destruct-symbols param
                                            js2-LP
-                                           'js2-function-param-face)
+                                           'js2-function-param)
               (push param params))
              ;; 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
                 (setq rest-param-at (length params)))
-              (js2-must-match js2-NAME "msg.no.parm")
-              (js2-record-face 'js2-function-param-face)
+              (js2-must-match-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)
@@ -7497,9 +7406,10 @@ 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)
-                       (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)))
@@ -7514,15 +7424,16 @@ 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")
-        (setf (js2-function-node-rp fn-node) (- js2-token-beg pos)))
+      (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))
       (dolist (p params)
         (js2-node-add-children fn-node p)
         (push p (js2-function-node-params fn-node))))))
 
-(defsubst js2-check-inconsistent-return-warning (fn-node name)
+(defun js2-check-inconsistent-return-warning (fn-node name)
   "Possibly show inconsistent-return warning.
 Last token scanned is the close-curly for the function body."
   (when (and js2-mode-show-strict-warnings
@@ -7531,67 +7442,62 @@ 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)
         (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-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-token-beg
-            name-end js2-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")))
-    (if (= js2-current-token js2-LP)  ; eventually matched LP?
-        (setq lp js2-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)))
-        ;; Function statements define a symbol in the enclosing scope
-        (js2-define-symbol js2-FUNCTION (js2-name-node-name name) fn-node))
+(defun js2-parse-function-stmt ()
+  (let ((pos (js2-current-token-beg))
+        (star-p (js2-match-token js2-MUL)))
+    (js2-must-match-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 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 star-p)
+              (js2-function-node-member-expr pn) member-expr)
+        pn)
+       (t
+        (js2-report-error "msg.no.paren.parms")
+        (make-js2-error-node))))))
+
+(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 star-p 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."
+  (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))))
+                                          :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)
+      (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)))
     (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.
@@ -7607,28 +7513,29 @@ Last token scanned is the close-curly for the function body."
           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)
         (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
@@ -7652,14 +7559,14 @@ 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)))
     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)
@@ -7687,7 +7594,7 @@ node are given relative start positions and correct lengths."
     (aset parsers js2-DEFAULT   #'js2-parse-default-xml-namespace)
     (aset parsers js2-DO        #'js2-parse-do)
     (aset parsers js2-FOR       #'js2-parse-for)
-    (aset parsers js2-FUNCTION  #'js2-function-parser)
+    (aset parsers js2-FUNCTION  #'js2-parse-function-stmt)
     (aset parsers js2-IF        #'js2-parse-if)
     (aset parsers js2-LC        #'js2-parse-block)
     (aset parsers js2-LET       #'js2-parse-let-stmt)
@@ -7704,7 +7611,7 @@ node are given relative start positions and correct lengths."
     parsers)
   "A vector mapping token types to parser functions.")
 
-(defsubst js2-parse-warn-missing-semi (beg end)
+(defun js2-parse-warn-missing-semi (beg end)
   (and js2-mode-show-strict-warnings
        js2-strict-missing-semi-warning
        (js2-add-strict-warning
@@ -7733,14 +7640,12 @@ 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)
          (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)
@@ -7753,23 +7658,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.
@@ -7779,10 +7684,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))
@@ -7794,19 +7699,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)
-        cond
-        if-true
-        if-false
-        else-pos
-        end
-        pn)
-    (js2-consume-token)
+  (let ((pos (js2-current-token-beg))
+        cond if-true if-false else-pos end pn)
     (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
@@ -7821,22 +7720,12 @@ 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."
-  (let ((pos js2-token-beg)
-        tt
-        pn
-        discriminant
-        has-default
-        case-expr
-        case-node
-        case-pos
-        cases
-        stmt
-        lp
-        rp)
-    (js2-consume-token)
+  "Parser for switch-statement.  Last matched token must be js2-SWITCH."
+  (let ((pos (js2-current-token-beg))
+        tt pn discriminant has-default case-expr case-node
+        case-pos cases stmt lp)
     (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
@@ -7846,15 +7735,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))
@@ -7869,7 +7758,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)
@@ -7889,11 +7778,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)
+        cond body)
     (js2-enter-loop pn)
     (unwind-protect
         (progn
@@ -7910,18 +7797,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)
+        cond body end)
     (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
@@ -7941,25 +7825,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
@@ -7967,18 +7850,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)))
@@ -7989,12 +7873,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
@@ -8035,7 +7919,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
@@ -8052,7 +7936,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)
@@ -8061,7 +7944,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
@@ -8069,27 +7952,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")
+            (js2-must-match-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)
@@ -8102,7 +7986,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)
@@ -8113,7 +7997,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
@@ -8133,10 +8017,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)
-        expr
-        pn)
-    (js2-consume-token)
+  (let ((pos (js2-current-token-beg))
+        expr pn)
     (if (= (js2-peek-token-or-eol) js2-EOL)
         ;; ECMAScript does not allow new lines before throw expression,
         ;; see bug 256617
@@ -8148,7 +8030,7 @@ Parses for, for-in, and for each-in statements."
     (js2-node-add-children pn expr)
     pn))
 
-(defsubst js2-match-jump-label-name (label-name)
+(defun js2-match-jump-label-name (label-name)
   "If break/continue specified a label, return that label's labeled stmt.
 Returns the corresponding `js2-labeled-stmt-node', or if LABEL-NAME
 does not match an existing label, reports an error and returns nil."
@@ -8159,19 +8041,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
@@ -8188,19 +8069,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
@@ -8220,14 +8100,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
@@ -8242,19 +8121,17 @@ 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)
-        expr
-        pn)
-    (js2-consume-token)
-    (setq expr (js2-parse-variables tt js2-token-beg)
+  (let ((tt (js2-current-token-type))
+        (pos (js2-current-token-beg))
+        expr pn)
+    (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))
     (js2-node-add-children pn expr)
     pn))
 
-(defsubst js2-wrap-with-expr-stmt (pos expr &optional add-child)
+(defun js2-wrap-with-expr-stmt (pos expr &optional add-child)
   (let ((pn (make-js2-expr-stmt-node :pos pos
                                      :len (js2-node-len expr)
                                      :type (if (js2-inside-function)
@@ -8267,10 +8144,8 @@ 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)
-        expr
-        pn)
+  (let ((pos (js2-current-token-beg))
+        expr pn)
     (if (= (js2-peek-token) js2-LP)
         ;; let expression in statement context
         (setq expr (js2-parse-let pos 'statement)
@@ -8282,7 +8157,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))
@@ -8296,18 +8171,21 @@ 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)
-        (before js2-end-flags)
-        (inside-function (js2-inside-function))
-        e
-        ret
-        name)
+  (let* ((pos (js2-current-token-beg))
+         (end (js2-current-token-end))
+         (before js2-end-flags)
+         (inside-function (js2-inside-function))
+         (gen-type (and inside-function (js2-function-node-generator-type
+                                         js2-current-script-or-fn)))
+         e ret name yield-star-p)
     (unless inside-function
       (js2-report-error (if (eq tt js2-RETURN)
                             "msg.bad.return"
                           "msg.bad.yield")))
-    (js2-consume-token)
+    (when (and inside-function
+               (eq gen-type 'STAR)
+               (js2-match-token js2-MUL))
+      (setq yield-star-p t))
     ;; 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)
@@ -8333,13 +8211,15 @@ but not BEFORE."
                (js2-now-all-set before js2-end-flags
                                 (logior js2-end-returns js2-end-returns-value)))
           (js2-add-strict-warning "msg.return.inconsistent" nil pos end)))
+     ((eq gen-type 'COMPREHENSION)
+      ;; FIXME: We should probably switch to saving and using lastYieldOffset,
+      ;; like SpiderMonkey does.
+      (js2-report-error "msg.syntax" nil pos 5))
      (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))
+                                     :value e
+                                     :star-p yield-star-p))
       (js2-node-add-children ret e)
       (unless expr-context
         (setq e ret
@@ -8348,8 +8228,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))
@@ -8357,50 +8238,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)
+Last token matched must be `js2-LC'."
+  (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
+;; 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-end) 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)
-        end len expr unary es)
-    (js2-consume-token)
+  (let ((pos (js2-current-token-beg))
+        end len expr unary)
     (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))
@@ -8417,7 +8294,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)
@@ -8440,36 +8317,30 @@ 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)
-        expr
-        stmt
-        pn
-        bundle
+  (let ((pos (js2-current-token-beg))
+        expr stmt 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.
+        (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
               (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))
@@ -8479,9 +8350,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.
@@ -8497,14 +8392,7 @@ in the first variable declaration.
 Returns the parsed `js2-var-decl-node' expression node."
   (let* ((result (make-js2-var-decl-node :decl-type decl-type
                                          :pos pos))
-         destructuring
-         kid-pos
-         tt
-         init
-         name
-         end
-         nbeg nend
-         vi
+         destructuring kid-pos tt init name end nbeg nend vi
          (continue t))
     ;; Example:
     ;; var foo = {a: 1, b: 2}, bar = [3, 4];
@@ -8513,21 +8401,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
-        (when (js2-must-match js2-NAME "msg.bad.var")
+        (js2-unget-token)
+        (when (js2-must-match-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))
@@ -8566,33 +8455,33 @@ 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 (js2-match-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
             (setf body (js2-parse-expr)
                   (js2-node-len pn) (- (js2-node-end body) pos)
                   (js2-let-node-body pn) body))
+          (setf (js2-let-node-vars pn) vars)
           (js2-node-add-children pn vars body))
       (js2-pop-scope))
     pn))
 
-(defsubst js2-define-new-symbol (decl-type name node &optional scope)
+(defun js2-define-new-symbol (decl-type name node &optional scope)
   (js2-scope-put-symbol (or scope js2-current-scope)
                         name
                         (make-js2-symbol decl-type name node)))
@@ -8643,6 +8532,21 @@ If NODE is non-nil, it is the AST node associated with the symbol."
       (js2-define-new-symbol decl-type name node))
      (t (js2-code-bug)))))
 
+(defun js2-parse-paren-expr-or-generator-comp ()
+  (let ((px-pos (js2-current-token-beg)))
+    (if (and (>= js2-language-version 200)
+             (js2-match-token js2-FOR))
+        (js2-parse-generator-comp px-pos)
+      (let* ((js2-in-for-init nil)
+             (expr (js2-parse-expr))
+             (pn (make-js2-paren-node :pos px-pos
+                                      :expr expr
+                                      :len (- (js2-current-token-end)
+                                              px-pos))))
+        (js2-node-add-children pn (js2-paren-node-expr pn))
+        (js2-must-match js2-RP "msg.no.paren")
+        pn))))
+
 (defun js2-parse-expr (&optional oneshot)
   (let* ((pn (js2-parse-assign-expr))
          (pos (js2-node-pos pn))
@@ -8651,7 +8555,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)
@@ -8666,24 +8570,26 @@ 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)
-        pn
-        left
-        right
-        op-pos)
+  (let ((tt (js2-get-token))
+        (pos (js2-current-token-beg))
+        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-peek-token))
-      (when (and (<= js2-first-assign tt)
-                 (<= tt js2-last-assign))
+            tt (js2-get-token))
+      (cond
+       ((and (<= js2-first-assign tt)
+             (<= tt js2-last-assign))
         ;; 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)
@@ -8695,10 +8601,17 @@ 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) nil)))
+       (t
+        (js2-unget-token)))
       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
@@ -8706,10 +8619,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)
-            if-true (js2-parse-assign-expr))
+      (setq q-pos (- (js2-current-token-beg) pos)
+            if-true (let (js2-in-for-init) (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
@@ -8727,11 +8640,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
@@ -8788,11 +8703,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
@@ -8803,14 +8718,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))
 
@@ -8822,11 +8738,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))
 
@@ -8835,11 +8750,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))
 
@@ -8851,19 +8765,18 @@ 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))
 
-(defsubst js2-make-unary (type parser &rest args)
+(defun js2-make-unary (type parser &rest args)
   "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
@@ -8872,7 +8785,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
@@ -8885,7 +8798,7 @@ to parse the operand (for prefix operators)."
   (list js2-NAME js2-GETPROP js2-GETELEM js2-GET_REF js2-CALL)
   "Node types that can be the operand of a ++ or -- operator.")
 
-(defsubst js2-check-bad-inc-dec (tt beg end unary)
+(defun js2-check-bad-inc-dec (tt beg end unary)
   (unless (memq (js2-node-type (js2-unary-node-operand unary))
                 js2-incrementable-node-types)
     (js2-report-error (if (= tt js2-INC)
@@ -8894,52 +8807,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 ()
@@ -8950,11 +8862,7 @@ mode or codegen mode, and generate the appropriate rewritten AST.
 IDE mode uses a rich AST that models the XML structure.  Codegen mode
 just concatenates everything and makes a new XML or XMLList out of it."
   (let ((tt (js2-get-first-xml-token))
-        pn-xml
-        pn
-        expr
-        kids
-        expr-pos
+        pn-xml pn expr kids expr-pos
         (continue t)
         (first-token t))
     (when (not (or (= tt js2-XML) (= tt js2-XMLEND)))
@@ -8966,10 +8874,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
@@ -8984,8 +8892,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))
@@ -8999,48 +8907,47 @@ just concatenates everything and makes a new XML or XMLList out of it."
 
 
 (defun js2-parse-argument-list ()
-  "Parse an argument list and return it as a lisp list of nodes.
+  "Parse an argument list and return it as a Lisp list of nodes.
 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")
       result)))
 
 (defun js2-parse-member-expr (&optional allow-call-syntax)
-  (let ((tt (js2-peek-token))
-        pn
-        pos
-        target
-        args
-        beg
-        end
-        init
-        tail)
+  (let ((tt (js2-current-token-type))
+        pn pos target args beg end init)
     (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)
             pn (make-js2-new-node :pos pos
                                   :target target
                                   :len (- end pos)))
+      (js2-highlight-function-call (js2-current-token))
       (js2-node-add-children pn target)
       (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))
@@ -9058,11 +8965,10 @@ 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))
+      (setq tt (js2-get-token))
       (cond
        ((or (= tt js2-DOT) (= tt js2-DOTDOT))
         (setq pn (js2-parse-property-access tt pn)))
@@ -9071,10 +8977,12 @@ Returns an expression tree that includes PN, the parent node."
        ((= tt js2-LB)
         (setq pn (js2-parse-element-get pn)))
        ((= tt js2-LP)
+        (js2-unget-token)
         (if allow-call-syntax
             (setq pn (js2-parse-function-call pn))
           (setq continue nil)))
        (t
+        (js2-unget-token)
         (setq continue nil))))
     (if (>= js2-highlight-level 2)
         (js2-parse-highlight-member-expr-node pn))
@@ -9084,13 +8992,10 @@ Returns an expression tree that includes PN, the parent node."
   "Parse a dot-query expression, e.g. foo.bar.(@name == 2)
 Last token parsed must be `js2-DOTQUERY'."
   (let ((pos (js2-node-pos pn))
-        op-pos
-        expr
-        end)
-    (js2-consume-token)
+        op-pos expr end)
     (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
@@ -9101,44 +9006,47 @@ 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)
+        rb expr)
     (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))
     pn))
 
+(defun js2-highlight-function-call (token)
+  (when (eq (js2-token-type token) js2-NAME)
+    (js2-record-face 'js2-function-call token)))
+
 (defun js2-parse-function-call (pn)
+  (js2-highlight-function-call (js2-current-token))
+  (js2-get-token)
   (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))
@@ -9147,12 +9055,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))
@@ -9161,10 +9068,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
@@ -9178,14 +9084,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)
@@ -9212,23 +9116,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.
@@ -9240,17 +9142,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))
-        ns
-        tt
-        ref
-        pn)
+        (name (js2-create-name-node t (js2-current-token-type) s))
+        ns tt 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
@@ -9258,8 +9157,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)))
@@ -9279,15 +9177,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
@@ -9305,40 +9203,32 @@ For instance, @[expr], @*::[expr], or ns::[expr]."
     (js2-parse-primary-expr)))
 
 (defun js2-parse-primary-expr ()
-  "Parses a literal (leaf) expression of some sort.
+  "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))
+      (js2-parse-function-expr))
      ((= tt js2-LB)
-      (js2-parse-array-literal))
+      (js2-parse-array-comp-or-literal))
      ((= 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
-            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)))
-      (js2-node-add-children pn (js2-paren-node-expr pn))
-      pn)
+      (js2-parse-paren-expr-or-generator-comp))
      ((= 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)
@@ -9347,14 +9237,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))))
@@ -9363,6 +9251,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))
@@ -9373,39 +9283,22 @@ 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)))))
 
-(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)))
-
-(defsubst js2-parse-warn-trailing-comma (msg pos elems comma-pos)
+    (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
    msg nil
    ;; back up from comma to beginning of line or array/objlit
@@ -9418,24 +9311,25 @@ array-literals, array comprehensions and regular expressions."
           (point)))
    comma-pos))
 
-(defun js2-parse-array-literal ()
-  (let ((pos js2-token-beg)
-        (end js2-token-end)
-        (after-lb-or-comma t)
-        after-comma
-        tt
-        elems
-        pn
+(defun js2-parse-array-comp-or-literal ()
+  (let ((pos (js2-current-token-beg)))
+    (if (and (>= js2-language-version 200)
+             (js2-match-token js2-FOR))
+        (js2-parse-array-comp pos)
+      (js2-parse-array-literal pos))))
+
+(defun js2-parse-array-literal (pos)
+  (let ((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
+      (js2-push-scope (make-js2-scope))) ; for the legacy 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)))
@@ -9443,17 +9337,15 @@ 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))
+        (when (and after-comma (< js2-language-version 170))
+          (js2-parse-warn-trailing-comma "msg.array.trailing.comma"
+                                         pos (remove nil elems) after-comma))
         (setq continue nil
-              end js2-token-end
               pn (make-js2-array-node :pos pos
                                       :len (- js2-ts-cursor pos)
                                       :elems (nreverse elems)))
-        (apply #'js2-node-add-children pn (js2-array-node-elems pn))
-        (when (and after-comma (not js2-is-in-destructuring))
-          (js2-parse-warn-trailing-comma "msg.array.trailing.comma"
-                                         pos elems after-comma)))
+        (apply #'js2-node-add-children pn (js2-array-node-elems pn)))
        ;; destructuring binding
        (js2-is-in-destructuring
         (push (if (or (= tt js2-LC)
@@ -9462,7 +9354,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)
@@ -9474,128 +9365,164 @@ 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)))
-
+              pn (js2-parse-legacy-array-comp (car elems) pos)))
        ;; another element
        (t
         (unless after-lb-or-comma
           (js2-report-error "msg.no.bracket.arg"))
-        (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
       (js2-pop-scope))
     pn))
 
-(defun js2-parse-array-comprehension (expr pos)
-  "Parse a JavaScript 1.7 Array Comprehension.
+(defun js2-parse-legacy-array-comp (expr pos)
+  "Parse a legacy array comprehension (JavaScript 1.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)
-      (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
-            (setf (js2-scope-parent-scope loop) prev)
-          ; first loop takes expr scope's parent
-          (setf (js2-scope-parent-scope (setq first loop))
-                (js2-scope-parent-scope js2-current-scope)))))
-    ;; 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
-            filter (js2-parse-condition)))
+  (let ((current-scope js2-current-scope)
+        loops first filter result)
+    (unwind-protect
+        (progn
+          (while (js2-match-token js2-FOR)
+            (let ((loop (make-js2-comp-loop-node)))
+              (js2-push-scope loop)
+              (push loop loops)
+              (js2-parse-comp-loop loop)))
+          ;; First loop takes expr scope's parent.
+          (setf (js2-scope-parent-scope (setq first (car (last loops))))
+                (js2-scope-parent-scope current-scope))
+          ;; Set expr scope's parent to the last loop.
+          (setf (js2-scope-parent-scope current-scope) (car loops))
+          (if (/= (js2-get-token) js2-IF)
+              (js2-unget-token)
+            (setq filter (js2-parse-condition))))
+      (dotimes (_ (1- (length loops)))
+        (js2-pop-scope)))
     (js2-must-match js2-RB "msg.no.bracket.arg" pos)
-    (setq result (make-js2-array-comp-node :pos pos
-                                           :len (- js2-ts-cursor pos)
-                                           :result expr
-                                           :loops (nreverse loops)
-                                           :filter (car filter)
-                                           :lp (js2-relpos (second filter) pos)
-                                           :rp (js2-relpos (third filter) pos)
-                                           :if-pos if-pos))
+    (setq result (make-js2-comp-node :pos pos
+                                     :len (- js2-ts-cursor pos)
+                                     :result expr
+                                     :loops (nreverse loops)
+                                     :filters (and filter (list (car filter)))
+                                     :form 'LEGACY_ARRAY))
     (apply #'js2-node-add-children result expr (car filter)
-           (js2-array-comp-node-loops result))
-    (setq js2-current-scope first) ; pop to the first loop
+           (js2-comp-node-loops result))
     result))
 
-(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)
-        (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
-    (js2-push-scope pn)
+(defun js2-parse-array-comp (pos)
+  "Parse an ES6 array comprehension.
+POS is the beginning of the LB token.
+We should have just parsed the 'for' keyword before calling this function."
+  (let ((pn (js2-parse-comprehension pos 'ARRAY)))
+    (js2-must-match js2-RB "msg.no.bracket.arg" pos)
+    pn))
+
+(defun js2-parse-generator-comp (pos)
+  (let* ((js2-nesting-of-function (1+ js2-nesting-of-function))
+         (js2-current-script-or-fn
+          (make-js2-function-node :generator-type 'COMPREHENSION))
+         (pn (js2-parse-comprehension pos 'STAR_GENERATOR)))
+    (js2-must-match js2-RP "msg.no.paren" pos)
+    pn))
+
+(defun js2-parse-comprehension (pos form)
+  (let (loops filters expr result)
     (unwind-protect
         (progn
-          (when (js2-match-token js2-NAME)
-            (if (string= js2-ts-string "each")
-                (progn
-                  (setq foreach-p t
-                        each-pos (- js2-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 tt (js2-peek-token))
-          (cond
-           ((or (= tt js2-LB)
-                (= tt js2-LC))
-            (setq iter (js2-parse-destruct-primary-expr))
-            (js2-define-destruct-symbols iter js2-LET
-                                         'font-lock-variable-name-face t))
-           ((js2-match-token js2-NAME)
-            (setq iter (js2-create-name-node)))
-           (t
-            (js2-report-error "msg.bad.var")))
-          ;; Define as a let since we want the scope of the variable to
-          ;; be restricted to the array comprehension
-          (if (js2-name-node-p iter)
-              (js2-define-symbol js2-LET (js2-name-node-name iter) pn t))
-          (if (or (js2-match-token js2-IN)
-                  (and (>= js2-language-version 200)
-                       (js2-match-contextual-kwd "of")
-                       (setq forof-p t)))
-              (setq in-pos (- js2-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)))
-          (setf (js2-node-pos pn) pos
-                (js2-node-len pn) (- js2-ts-cursor pos)
-                (js2-array-comp-loop-node-iterator pn) iter
-                (js2-array-comp-loop-node-object pn) obj
-                (js2-array-comp-loop-node-in-pos pn) in-pos
-                (js2-array-comp-loop-node-each-pos pn) each-pos
-                (js2-array-comp-loop-node-foreach-p pn) foreach-p
-                (js2-array-comp-loop-node-forof-p pn) forof-p
-                (js2-array-comp-loop-node-lp pn) lp
-                (js2-array-comp-loop-node-rp pn) rp)
-          (js2-node-add-children pn iter obj))
-      (js2-pop-scope))
+          (js2-unget-token)
+          (while (js2-match-token js2-FOR)
+            (let ((loop (make-js2-comp-loop-node)))
+              (js2-push-scope loop)
+              (push loop loops)
+              (js2-parse-comp-loop loop)))
+          (while (js2-match-token js2-IF)
+            (push (car (js2-parse-condition)) filters))
+          (setq expr (js2-parse-assign-expr)))
+      (dolist (_ loops)
+        (js2-pop-scope)))
+    (setq result (make-js2-comp-node :pos pos
+                                     :len (- js2-ts-cursor pos)
+                                     :result expr
+                                     :loops (nreverse loops)
+                                     :filters (nreverse filters)
+                                     :form form))
+    (apply #'js2-node-add-children result (js2-comp-node-loops result))
+    (apply #'js2-node-add-children result expr (js2-comp-node-filters result))
+    result))
+
+(defun js2-parse-comp-loop (pn &optional only-of-p)
+  "Parse a 'for [each] (foo [in|of] bar)' expression in an Array comprehension.
+The current token should be the initial FOR.
+If ONLY-OF-P is non-nil, only the 'for (foo of bar)' form is allowed."
+  (let ((pos (js2-comp-loop-node-pos pn))
+        tt iter obj foreach-p forof-p in-pos each-pos lp rp)
+    (when (and (not only-of-p) (js2-match-token js2-NAME))
+      (if (string= (js2-current-token-string) "each")
+          (progn
+            (setq foreach-p t
+                  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-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))
+     ((js2-match-token js2-NAME)
+      (setq iter (js2-create-name-node)))
+     (t
+      (js2-report-error "msg.bad.var")))
+    ;; Define as a let since we want the scope of the variable to
+    ;; be restricted to the array comprehension
+    (if (js2-name-node-p iter)
+        (js2-define-symbol js2-LET (js2-name-node-name iter) pn t))
+    (if (or (and (not only-of-p) (js2-match-token js2-IN))
+            (and (>= js2-language-version 200)
+                 (js2-match-contextual-kwd "of")
+                 (setq forof-p t)))
+        (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-current-token-beg) pos)))
+    (setf (js2-node-pos pn) pos
+          (js2-node-len pn) (- js2-ts-cursor pos)
+          (js2-comp-loop-node-iterator pn) iter
+          (js2-comp-loop-node-object pn) obj
+          (js2-comp-loop-node-in-pos pn) in-pos
+          (js2-comp-loop-node-each-pos pn) each-pos
+          (js2-comp-loop-node-foreach-p pn) foreach-p
+          (js2-comp-loop-node-forof-p pn) forof-p
+          (js2-comp-loop-node-lp pn) lp
+          (js2-comp-loop-node-rp pn) rp)
+    (js2-node-add-children pn iter obj)
     pn))
 
 (defun js2-parse-object-literal ()
-  (let ((pos js2-token-beg)
-        tt
-        elems
-        result
-        after-comma
+  (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) {...}}
+       ;; {foo: ...}, {'foo': ...}, {foo, bar, ...},
+       ;; {get foo() {...}}, or {set foo(x) {...}}
        ((or (js2-valid-prop-name-token tt)
             (= tt js2-STRING))
         (setq after-comma nil
@@ -9606,11 +9533,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"
@@ -9620,7 +9547,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
@@ -9632,21 +9559,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
@@ -9681,7 +9607,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
@@ -9709,9 +9635,8 @@ POS is the start position of the `get' or `set' keyword.
 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)))
+        result end
+        (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")
@@ -9727,19 +9652,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)))
@@ -9793,28 +9715,6 @@ of continued expressions.")
   (regexp-opt '("var" "let" "const") 'words)
   "Regular expression matching variable declaration keywords.")
 
-;; This function has horrible results if you're typing an array
-;; such as [[1, 2], [3, 4], [5, 6]].  Bounce indenting -really- sucks
-;; in conjunction with electric-indent, so just disabling it.
-(defsubst js2-code-at-bol-p ()
-  "Return t if the first character on line is non-whitespace."
-  nil)
-
-(defun js2-insert-and-indent (key)
-  "Run command bound to key and indent current line. Runs the command
-bound to KEY in the global keymap and indents the current line."
-  (interactive (list (this-command-keys)))
-  (let ((cmd (lookup-key (current-global-map) key)))
-    (if (commandp cmd)
-        (call-interactively cmd)))
-  ;; don't do the electric keys inside comments or strings,
-  ;; and don't do bounce-indent with them.
-  (let ((parse-state (syntax-ppss (point)))
-        (js2-bounce-indent-p (js2-code-at-bol-p)))
-    (unless (or (nth 3 parse-state)
-                (nth 4 parse-state))
-      (indent-according-to-mode))))
-
 (defun js2-re-search-forward-inner (regexp &optional bound count)
   "Auxiliary function for `js2-re-search-forward'."
   (let (parse saved-point)
@@ -9825,7 +9725,7 @@ bound to KEY in the global keymap and indents the current line."
                     (syntax-ppss (point))))
       (cond ((nth 3 parse)
              (re-search-forward
-              (concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
+              (concat "\\(\\=\\|[^\\]\\|^\\)" (string (nth 3 parse)))
               (save-excursion (end-of-line) (point)) t))
             ((nth 7 parse)
              (forward-line))
@@ -9838,19 +9738,17 @@ bound to KEY in the global keymap and indents the current line."
   (point))
 
 (defun js2-re-search-forward (regexp &optional bound noerror count)
-  "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)))))
+  "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)))
     (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
@@ -9858,16 +9756,14 @@ comments have been removed."
 
 (defun js2-re-search-backward-inner (regexp &optional bound count)
   "Auxiliary function for `js2-re-search-backward'."
-  (let (parse saved-point)
+  (let (parse)
     (while (> count 0)
       (re-search-backward regexp bound)
-      (setq parse (if saved-point
-                      (parse-partial-sexp saved-point (point))
-                    (syntax-ppss (point))))
+      (setq parse (syntax-ppss (point)))
       (cond ((nth 3 parse)
              (re-search-backward
               (concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
-              (save-excursion (beginning-of-line) (point)) t))
+              (line-beginning-position) t))
             ((nth 7 parse)
              (goto-char (nth 8 parse)))
             ((or (nth 4 parse)
@@ -9878,36 +9774,32 @@ comments have been removed."
   (point))
 
 (defun js2-re-search-backward (regexp &optional bound noerror count)
-  "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)))))
+  "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)))
     (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
          (error (error-message-string err)))))))
 
 (defun js2-looking-at-operator-p ()
-  "Return non-nil if text after point is an operator (that is not
-a comma)."
-  (save-match-data
-    (and (looking-at js2-indent-operator-re)
-         (or (not (looking-at ":"))
-             (save-excursion
-               (and (js2-re-search-backward "[?:{]\\|\\<case\\>" nil t)
-                    (looking-at "?")))))))
+  "Return non-nil if text after point is a non-comma operator."
+  (and (looking-at js2-indent-operator-re)
+       (or (not (looking-at ":"))
+           (save-excursion
+             (and (js2-re-search-backward "[?:{]\\|\\<case\\>" nil t)
+                  (looking-at "?"))))))
 
 (defun js2-continued-expression-p ()
-  "Returns non-nil if the current line continues an expression."
+  "Return non-nil if the current line continues an expression."
   (save-excursion
     (back-to-indentation)
     (or (js2-looking-at-operator-p)
@@ -9926,31 +9818,30 @@ a comma)."
             (not (looking-at "\\*\\|++\\|--\\|/[/*]")))))))
 
 (defun js2-end-of-do-while-loop-p ()
-  "Returns non-nil if word after point is `while' of a do-while
+  "Return non-nil if word after point is `while' of a do-while
 statement, else returns nil. A braceless do-while statement
 spanning several lines requires that the start of the loop is
 indented to the same column as the current line."
   (interactive)
   (save-excursion
-    (save-match-data
-      (when (looking-at "\\s-*\\<while\\>")
-        (if (save-excursion
-              (skip-chars-backward "[ \t\n]*}")
-              (looking-at "[ \t\n]*}"))
-            (save-excursion
-              (backward-list) (backward-word 1) (looking-at "\\<do\\>"))
-          (js2-re-search-backward "\\<do\\>" (point-at-bol) t)
-          (or (looking-at "\\<do\\>")
-              (let ((saved-indent (current-indentation)))
-                (while (and (js2-re-search-backward "^[ \t]*\\<" nil t)
-                            (/= (current-indentation) saved-indent)))
-                (and (looking-at "[ \t]*\\<do\\>")
-                     (not (js2-re-search-forward
-                           "\\<while\\>" (point-at-eol) t))
-                     (= (current-indentation) saved-indent)))))))))
+    (when (looking-at "\\s-*\\<while\\>")
+      (if (save-excursion
+            (skip-chars-backward "[ \t\n]*}")
+            (looking-at "[ \t\n]*}"))
+          (save-excursion
+            (backward-list) (backward-word 1) (looking-at "\\<do\\>"))
+        (js2-re-search-backward "\\<do\\>" (point-at-bol) t)
+        (or (looking-at "\\<do\\>")
+            (let ((saved-indent (current-indentation)))
+              (while (and (js2-re-search-backward "^[ \t]*\\<" nil t)
+                          (/= (current-indentation) saved-indent)))
+              (and (looking-at "[ \t]*\\<do\\>")
+                   (not (js2-re-search-forward
+                         "\\<while\\>" (point-at-eol) t))
+                   (= (current-indentation) saved-indent))))))))
 
 (defun js2-multiline-decl-indentation ()
-  "Returns the declaration indentation column if the current line belongs
+  "Return the declaration indentation column if the current line belongs
 to a multiline declaration statement.  See `js2-pretty-multiline-declarations'."
   (let (forward-sexp-function ; use Lisp version
         at-opening-bracket)
@@ -9966,13 +9857,12 @@ to a multiline declaration statement.  See `js2-pretty-multiline-declarations'."
                         (js2-backward-sws)
                         (or (eq (char-before) ?,)
                             (and (not (eq (char-before) ?\;))
-                                 (and
-                                  (prog2 (skip-chars-backward "[[:punct:]]")
-                                      (looking-at js2-indent-operator-re)
-                                    (js2-backward-sws))
-                                  (not (eq (char-before) ?\;))))
+                                 (prog2 (skip-syntax-backward ".")
+                                     (looking-at js2-indent-operator-re)
+                                   (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)
@@ -9980,10 +9870,10 @@ to a multiline declaration statement.  See `js2-pretty-multiline-declarations'."
           (1+ (current-column)))))))
 
 (defun js2-ctrl-statement-indentation ()
-  "Returns the proper indentation of the current line if it
-starts the body of a control statement without braces, else
-returns nil."
-  (let (forward-sexp-function)  ; temporarily unbind it
+  "Return the proper indentation of current line if it is a control statement.
+Returns an indentation if this line starts the body of a control
+statement without braces, else returns nil."
+  (let (forward-sexp-function)
     (save-excursion
       (back-to-indentation)
       (when (and (not (js2-same-line (point-min)))
@@ -9998,6 +9888,8 @@ returns nil."
                      (skip-chars-backward " \t" (point-at-bol)))
                    (let ((pt (point)))
                      (back-to-indentation)
+                     (when (looking-at "}[ \t]*")
+                       (goto-char (match-end 0)))
                      (and (looking-at js2-possibly-braceless-keywords-re)
                           (= (match-end 0) pt)
                           (not (js2-end-of-do-while-loop-p))))))
@@ -10015,7 +9907,7 @@ In particular, return the buffer position of the first `for' kwd."
           (forward-char 1)
           (js2-forward-sws)
           (if (looking-at "[[{]")
-              (let (forward-sexp-function) ; use lisp version
+              (let (forward-sexp-function) ; use Lisp version
                 (forward-sexp)             ; skip destructuring form
                 (js2-forward-sws)
                 (if (and (/= (char-after) ?,) ; regular array
@@ -10046,13 +9938,19 @@ 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\\>[^:]")
+                              (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)))
+           (bracket (nth 1 parse-status))
+           beg indent)
       (cond
        ;; indent array comprehension continuation lines specially
        ((and bracket
@@ -10082,12 +9980,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)
@@ -10120,7 +10024,7 @@ In particular, return the buffer position of the first `for' kwd."
   (interactive)
   (while (forward-comment 1)))
 
-(defsubst js2-current-indent (&optional pos)
+(defun js2-current-indent (&optional pos)
   "Return column of indentation on current line.
 If POS is non-nil, go to that point and return indentation for that line."
   (save-excursion
@@ -10129,15 +10033,14 @@ If POS is non-nil, go to that point and return indentation for that line."
     (back-to-indentation)
     (current-column)))
 
-(defsubst js2-arglist-close ()
+(defun js2-arglist-close ()
   "Return non-nil if we're on a line beginning with a close-paren/brace."
-  (save-match-data
-    (save-excursion
-      (goto-char (point-at-bol))
-      (js2-forward-sws)
-      (looking-at "[])}]"))))
+  (save-excursion
+    (goto-char (point-at-bol))
+    (js2-forward-sws)
+    (looking-at "[])}]")))
 
-(defsubst js2-indent-looks-like-label-p ()
+(defun js2-indent-looks-like-label-p ()
   (goto-char (point-at-bol))
   (js2-forward-sws)
   (looking-at (concat js2-mode-identifier-re ":")))
@@ -10154,11 +10057,11 @@ If POS is non-nil, go to that point and return indentation for that line."
      (save-excursion
        (js2-indent-looks-like-label-p)))))
 
-;; if prev line looks like foobar({ then we're passing an object
+;; If prev line looks like foobar({ then we're passing an object
 ;; literal to a function call, and people pretty much always want to
 ;; de-dent back to the previous line, so move the 'basic-offset'
 ;; position to the front.
-(defsubst js2-indent-objlit-arg-p (parse-status)
+(defun js2-indent-objlit-arg-p (parse-status)
   (save-excursion
     (back-to-indentation)
     (js2-backward-sws)
@@ -10169,24 +10072,15 @@ If POS is non-nil, go to that point and return indentation for that line."
            (skip-chars-backward " \t")
            (eq (char-before) ?\()))))
 
-(defsubst js2-indent-case-block-p ()
+(defun js2-indent-case-block-p ()
   (save-excursion
     (back-to-indentation)
     (js2-backward-sws)
     (goto-char (point-at-bol))
     (skip-chars-forward " \t")
-    (save-match-data
-      (looking-at "case\\s-.+:"))))
-
-(defsubst js2-syntax-bol ()
-  "Return the point at the first non-whitespace char on the line.
-Returns `point-at-bol' if the line is empty."
-  (save-excursion
-    (beginning-of-line)
-    (skip-chars-forward " \t")
-    (point)))
+    (looking-at "case\\s-.+:")))
 
-(defun js2-bounce-indent (normal-col parse-status backwards)
+(defun js2-bounce-indent (normal-col parse-status &optional backwards)
   "Cycle among alternate computed indentation positions.
 PARSE-STATUS is the result of `parse-partial-sexp' from the beginning
 of the buffer to the current point.  NORMAL-COL is the indentation
@@ -10199,61 +10093,50 @@ in reverse."
         (current-line (save-excursion
                         (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)
+        positions pos main-pos anchor arglist-cont same-indent
+        basic-offset computed-pos)
     ;; temporarily don't record undo info, if user requested this
-    (if js2-mode-indent-inhibit-undo
-        (setq buffer-undo-list t))
+    (when js2-mode-indent-inhibit-undo
+      (setq buffer-undo-list t))
     (unwind-protect
         (progn
-          ;; first likely point:  indent from beginning of previous code line
+          ;; First likely point:  indent from beginning of previous code line
           (push (setq basic-offset
                       (+ (save-excursion
                            (back-to-indentation)
                            (js2-backward-sws)
                            (back-to-indentation)
-                           (setq prev-line-col (current-column)))
+                           (current-column))
                          js2-basic-offset))
                 positions)
 
-          ;; (first + epsilon) likely point:  indent 2x from beginning of
-          ;; previous code line.  Some companies like this approach.  Ahem.
-          ;; Seriously, though -- 4-space indent for expression continuation
-          ;; lines isn't a bad idea.  We should eventually implement it
-          ;; that way.
+          ;; (First + epsilon) likely point:  indent 2x from beginning of
+          ;; previous code line.  Google does it this way.
           (push (setq basic-offset
                       (+ (save-excursion
                            (back-to-indentation)
                            (js2-backward-sws)
                            (back-to-indentation)
-                           (setq prev-line-col (current-column)))
+                           (current-column))
                          (* 2 js2-basic-offset)))
                 positions)
 
-          ;; second likely point:  indent from assign-expr RHS.  This
+          ;; Second likely point:  indent from assign-expr RHS.  This
           ;; is just a crude guess based on finding " = " on the previous
           ;; line containing actual code.
           (setq pos (save-excursion
-                      (save-match-data
-                        (forward-line -1)
-                        (goto-char (point-at-bol))
-                        (when (re-search-forward "\\s-+\\(=\\)\\s-+"
-                                                 (point-at-eol) t)
-                          (goto-char (match-end 1))
-                          (skip-chars-forward " \t\r\n")
-                          (current-column)))))
+                      (forward-line -1)
+                      (goto-char (point-at-bol))
+                      (when (re-search-forward "\\s-+\\(=\\)\\s-+"
+                                               (point-at-eol) t)
+                        (goto-char (match-end 1))
+                        (skip-chars-forward " \t\r\n")
+                        (current-column))))
           (when pos
             (incf pos js2-basic-offset)
             (push pos positions))
 
-          ;; third likely point:  same indent as previous line of code.
+          ;; Third likely point:  same indent as previous line of code.
           ;; Make it the first likely point if we're not on an
           ;; arglist-close line and previous line ends in a comma, or
           ;; both this line and prev line look like object-literal
@@ -10277,7 +10160,7 @@ in reverse."
                 (setq same-indent pos))
             (push pos positions))
 
-          ;; fourth likely point:  first preceding code with less indentation
+          ;; Fourth likely point:  first preceding code with less indentation.
           ;; than the immediately preceding code line.
           (setq pos (save-excursion
                       (back-to-indentation)
@@ -10358,30 +10241,26 @@ cycles between the computed indentation positions in reverse order."
   (interactive)
   (js2-indent-line t))
 
-(defsubst js2-1-line-comment-continuation-p ()
+(defun js2-1-line-comment-continuation-p ()
   "Return t if we're in a 1-line comment continuation.
 If so, we don't ever want to use bounce-indent."
   (save-excursion
-    (save-match-data
-      (and (progn
-             (forward-line 0)
-             (looking-at "\\s-*//"))
-           (progn
-             (forward-line -1)
-             (forward-line 0)
-             (when (looking-at "\\s-*$")
-               (js2-backward-sws)
-               (forward-line 0))
-             (looking-at "\\s-*//"))))))
+    (and (progn
+           (forward-line 0)
+           (looking-at "\\s-*//"))
+         (progn
+           (forward-line -1)
+           (forward-line 0)
+           (when (looking-at "\\s-*$")
+             (js2-backward-sws)
+             (forward-line 0))
+           (looking-at "\\s-*//")))))
 
 (defun js2-indent-line (&optional bounce-backwards)
   "Indent the current line as JavaScript source text."
   (interactive)
-  (let (parse-status
-        offset
-        indent-col
-        moved
-        ;; don't whine about errors/warnings when we're indenting.
+  (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))
     (setq parse-status (save-excursion
@@ -10393,7 +10272,7 @@ If so, we don't ever want to use bounce-indent."
      (if (nth 4 parse-status)
          (js2-lineup-comment parse-status)
        (setq indent-col (js2-proper-indentation parse-status))
-       ;; see comments below about js2-mode-last-indented-line
+       ;; See comments below about `js2-mode-last-indented-line'.
        (cond
         ;; bounce-indenting is disabled during electric-key indent.
         ;; It doesn't work well on first line of buffer.
@@ -10415,40 +10294,184 @@ If so, we don't ever want to use bounce-indent."
     (indent-region start end nil) ; nil for byte-compiler
     (js2-mode-edit start end (- end start))))
 
-;;;###autoload (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode))
+(defvar js2-minor-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "C-c C-`") #'js2-next-error)
+    (define-key map [mouse-1] #'js2-mode-show-node)
+    map)
+  "Keymap used when `js2-minor-mode' is active.")
 
 ;;;###autoload
-(defun js2-mode ()
-  "Major mode for editing JavaScript code.
+(define-minor-mode js2-minor-mode
+  "Minor mode for running js2 as a background linter.
+This allows you to use a different major mode for JavaScript editing,
+such as `js-mode', while retaining the asynchronous error/warning
+highlighting features of `js2-mode'."
+  :group 'js2-mode
+  :lighter " js-lint"
+  (if js2-minor-mode
+      (js2-minor-mode-enter)
+    (js2-minor-mode-exit)))
+
+(defun js2-minor-mode-enter ()
+  "Initialization for `js2-minor-mode'."
+  (set (make-local-variable 'max-lisp-eval-depth)
+       (max max-lisp-eval-depth 3000))
+  (setq next-error-function #'js2-next-error)
+  (js2-set-default-externs)
+  ;; Experiment:  make reparse-delay longer for longer files.
+  (if (plusp js2-dynamic-idle-timer-adjust)
+      (setq js2-idle-timer-delay
+            (* js2-idle-timer-delay
+               (/ (point-max) js2-dynamic-idle-timer-adjust))))
+  (setq js2-mode-buffer-dirty-p t
+        js2-mode-parsing nil)
+  (set (make-local-variable 'js2-highlight-level) 0) ; no syntax highlighting
+  (add-hook 'after-change-functions #'js2-minor-mode-edit nil t)
+  (add-hook 'change-major-mode-hook #'js2-minor-mode-exit nil t)
+  (when js2-include-jslint-globals
+    (add-hook 'js2-post-parse-callbacks 'js2-apply-jslint-globals nil t))
+  (run-hooks 'js2-init-hook)
+  (js2-reparse))
+
+(defun js2-minor-mode-exit ()
+  "Turn off `js2-minor-mode'."
+  (setq next-error-function nil)
+  (remove-hook 'after-change-functions #'js2-mode-edit t)
+  (remove-hook 'change-major-mode-hook #'js2-minor-mode-exit t)
+  (when js2-mode-node-overlay
+    (delete-overlay js2-mode-node-overlay)
+    (setq js2-mode-node-overlay nil))
+  (js2-remove-overlays)
+  (remove-hook 'js2-post-parse-callbacks 'js2-apply-jslint-globals t)
+  (setq js2-mode-ast nil))
+
+(defvar js2-source-buffer nil "Linked source buffer for diagnostics view")
+(make-variable-buffer-local 'js2-source-buffer)
+
+(defun* js2-display-error-list ()
+  "Display a navigable buffer listing parse errors/warnings."
+  (interactive)
+  (unless (js2-have-errors-p)
+    (message "No errors")
+    (return-from js2-display-error-list))
+  (labels ((annotate-list
+            (lst type)
+            "Add diagnostic TYPE and line number to errs list"
+            (mapcar (lambda (err)
+                      (list err type (line-number-at-pos (nth 1 err))))
+                    lst)))
+    (let* ((srcbuf (current-buffer))
+           (errbuf (get-buffer-create "*js-lint*"))
+           (errors (annotate-list
+                    (when js2-mode-ast (js2-ast-root-errors js2-mode-ast))
+                    'js2-error))  ; must be a valid face name
+           (warnings (annotate-list
+                      (when js2-mode-ast (js2-ast-root-warnings js2-mode-ast))
+                      'js2-warning))  ; must be a valid face name
+           (all-errs (sort (append errors warnings)
+                           (lambda (e1 e2) (< (cadar e1) (cadar e2))))))
+      (with-current-buffer errbuf
+        (let ((inhibit-read-only t))
+          (erase-buffer)
+          (dolist (err all-errs)
+            (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
+               'follow-link "\C-m"
+               'action 'js2-error-buffer-jump
+               'js2-msg (js2-get-msg msg-key)
+               'js2-pos beg)
+              (insert "\n"))))
+        (js2-error-buffer-mode)
+        (setq js2-source-buffer srcbuf)
+        (pop-to-buffer errbuf)
+        (goto-char (point-min))
+        (unless (eobp)
+          (js2-error-buffer-view))))))
+
+(defvar js2-error-buffer-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "n" #'js2-error-buffer-next)
+    (define-key map "p" #'js2-error-buffer-prev)
+    (define-key map (kbd "RET") #'js2-error-buffer-jump)
+    (define-key map "o" #'js2-error-buffer-view)
+    (define-key map "q" #'js2-error-buffer-quit)
+    map)
+  "Keymap used for js2 diagnostics buffers.")
+
+(defun js2-error-buffer-mode ()
+  "Major mode for js2 diagnostics buffers.
+Selecting an error will jump it to the corresponding source-buffer error.
+\\{js2-error-buffer-mode-map}"
+  (interactive)
+  (setq major-mode 'js2-error-buffer-mode
+        mode-name "JS Lint Diagnostics")
+  (use-local-map js2-error-buffer-mode-map)
+  (setq truncate-lines t)
+  (set-buffer-modified-p nil)
+  (setq buffer-read-only t)
+  (run-hooks 'js2-error-buffer-mode-hook))
+
+(defun js2-error-buffer-next ()
+  "Move to next error and view it."
+  (interactive)
+  (when (zerop (forward-line 1))
+    (js2-error-buffer-view)))
 
-\\{js2-mode-map}"
+(defun js2-error-buffer-prev ()
+  "Move to previous error and view it."
   (interactive)
-  (kill-all-local-variables)
-  (set-syntax-table js2-mode-syntax-table)
-  (use-local-map js2-mode-map)
-  (make-local-variable 'comment-start)
-  (make-local-variable 'comment-end)
-  (make-local-variable 'comment-start-skip)
-  (setq major-mode 'js2-mode
-        mode-name "JavaScript-IDE"
-        comment-start "//"  ; used by comment-region; don't change it
-        comment-end "")
-  (setq local-abbrev-table js2-mode-abbrev-table)
+  (when (zerop (forward-line -1))
+    (js2-error-buffer-view)))
+
+(defun js2-error-buffer-quit ()
+  "Kill the current buffer."
+  (interactive)
+  (kill-buffer))
+
+(defun js2-error-buffer-jump (&rest ignored)
+  "Jump cursor to current error in source buffer."
+  (interactive)
+  (when (js2-error-buffer-view)
+    (pop-to-buffer js2-source-buffer)))
+
+(defun js2-error-buffer-view ()
+  "Scroll source buffer to show error at current line."
+  (interactive)
+  (cond
+   ((not (eq major-mode 'js2-error-buffer-mode))
+    (message "Not in a js2 errors buffer"))
+   ((not (buffer-live-p js2-source-buffer))
+    (message "Source buffer has been killed"))
+   ((not (wholenump (get-text-property (point) 'js2-pos)))
+    (message "There does not seem to be an error here"))
+   (t
+    (let ((pos (get-text-property (point) 'js2-pos))
+          (msg (get-text-property (point) 'js2-msg)))
+      (save-selected-window
+        (pop-to-buffer js2-source-buffer)
+        (goto-char pos)
+        (message msg))))))
+
+;;;###autoload
+(define-derived-mode js2-mode prog-mode "Javascript-IDE"
+  ;; FIXME: Should derive from js-mode.
+  "Major mode for editing JavaScript code."
+  ;; Used by comment-region; don't change it.
+  (set (make-local-variable 'comment-start) "//")
+  (set (make-local-variable 'comment-end) "")
+  (set (make-local-variable 'comment-start-skip) js2-comment-start-skip)
   (set (make-local-variable 'max-lisp-eval-depth)
        (max max-lisp-eval-depth 3000))
   (set (make-local-variable 'indent-line-function) #'js2-indent-line)
   (set (make-local-variable 'indent-region-function) #'js2-indent-region)
-
-  ;; I tried an "improvement" to `c-fill-paragraph' that worked out badly
-  ;; on most platforms other than the one I originally wrote it on.
-  ;; So it's back to `c-fill-paragraph'.
   (set (make-local-variable 'fill-paragraph-function) #'c-fill-paragraph)
-
-  (add-hook 'before-save-hook #'js2-before-save nil t)
-  (set (make-local-variable 'next-error-function) #'js2-next-error)
+  (set (make-local-variable 'comment-line-break-function) #'js2-line-break)
   (set (make-local-variable 'beginning-of-defun-function) #'js2-beginning-of-defun)
   (set (make-local-variable 'end-of-defun-function) #'js2-end-of-defun)
-  ;; we un-confuse `parse-partial-sexp' by setting syntax-table properties
+  ;; We un-confuse `parse-partial-sexp' by setting syntax-table properties
   ;; for characters inside regexp literals.
   (set (make-local-variable 'parse-sexp-lookup-properties) t)
   ;; this is necessary to make `show-paren-function' work properly
@@ -10456,13 +10479,17 @@ If so, we don't ever want to use bounce-indent."
   ;; needed for M-x rgrep, among other things
   (put 'js2-mode 'find-tag-default-function #'js2-mode-find-tag)
 
+  (set (make-local-variable 'electric-indent-chars)
+       (append "{}()[]:;,*." electric-indent-chars))
+  (set (make-local-variable 'electric-layout-rules)
+       '((?\; . after) (?\{ . after) (?\} . before)))
+
   ;; some variables needed by cc-engine for paragraph-fill, etc.
   (setq c-comment-prefix-regexp js2-comment-prefix-regexp
         c-comment-start-regexp "/[*/]\\|\\s|"
         c-line-comment-starter "//"
         c-paragraph-start js2-paragraph-start
         c-paragraph-separate "$"
-        comment-start-skip js2-comment-start-skip
         c-syntactic-ws-start js2-syntactic-ws-start
         c-syntactic-ws-end js2-syntactic-ws-end
         c-syntactic-eol js2-syntactic-eol)
@@ -10476,45 +10503,35 @@ If so, we don't ever want to use bounce-indent."
     (make-local-variable 'adaptive-fill-regexp)
     (c-setup-paragraph-variables))
 
-  (setq js2-default-externs
-        (append js2-ecma-262-externs
-                (if js2-include-browser-externs
-                    js2-browser-externs)
-                (if js2-include-gears-externs
-                    js2-gears-externs)
-                (if js2-include-rhino-externs
-                     js2-rhino-externs)))
-
-  ;; We do our own syntax highlighting based on the parse tree.
-  ;; However, we want minor modes that add keywords to highlight properly
-  ;; (examples:  doxymacs, column-marker).
-  ;; To customize highlighted keywords, use `font-lock-add-keywords'.
   (setq font-lock-defaults '(nil t))
 
   ;; Experiment:  make reparse-delay longer for longer files.
-  (if (plusp js2-dynamic-idle-timer-adjust)
-      (setq js2-idle-timer-delay
-            (* js2-idle-timer-delay
-               (/ (point-max) js2-dynamic-idle-timer-adjust))))
+  (when (plusp js2-dynamic-idle-timer-adjust)
+    (setq js2-idle-timer-delay
+          (* js2-idle-timer-delay
+             (/ (point-max) js2-dynamic-idle-timer-adjust))))
 
   (add-hook 'change-major-mode-hook #'js2-mode-exit nil t)
   (add-hook 'after-change-functions #'js2-mode-edit nil t)
   (setq imenu-create-index-function #'js2-mode-create-imenu-index)
+  (setq next-error-function #'js2-next-error)
   (imenu-add-to-menubar (concat "IM-" mode-name))
-  (when js2-mirror-mode
-    (js2-enter-mirror-mode))
   (add-to-invisibility-spec '(js2-outline . t))
   (set (make-local-variable 'line-move-ignore-invisible) t)
   (set (make-local-variable 'forward-sexp-function) #'js2-mode-forward-sexp)
 
-  (if (fboundp 'run-mode-hooks)
-      (run-mode-hooks 'js2-mode-hook)
-    (run-hooks 'js2-mode-hook))
-
   (setq js2-mode-functions-hidden nil
         js2-mode-comments-hidden nil
         js2-mode-buffer-dirty-p t
         js2-mode-parsing nil)
+
+  (js2-set-default-externs)
+
+  (when js2-include-jslint-globals
+    (add-hook 'js2-post-parse-callbacks 'js2-apply-jslint-globals nil t))
+
+  (run-hooks 'js2-init-hook)
+
   (js2-reparse))
 
 (defun js2-mode-exit ()
@@ -10528,20 +10545,10 @@ If so, we don't ever want to use bounce-indent."
   (remove-hook 'change-major-mode-hook #'js2-mode-exit t)
   (remove-from-invisibility-spec '(js2-outline . t))
   (js2-mode-show-all)
-  (js2-with-unmodifying-text-property-changes
+  (with-silent-modifications
     (js2-clear-face (point-min) (point-max))))
 
-(defun js2-before-save ()
-  "Clean up whitespace before saving file.
-You can disable this by customizing `js2-cleanup-whitespace'."
-  (when js2-cleanup-whitespace
-    (let ((col (current-column)))
-      (delete-trailing-whitespace)
-      ;; don't change trailing whitespace on current line
-      (unless (eq (current-column) col)
-        (indent-to col)))))
-
-(defsubst js2-mode-reset-timer ()
+(defun js2-mode-reset-timer ()
   "Cancel any existing parse timer and schedule a new one."
   (if js2-mode-parse-timer
       (cancel-timer js2-mode-parse-timer))
@@ -10556,13 +10563,14 @@ You can disable this by customizing `js2-cleanup-whitespace'."
 (defun js2-mode-idle-reparse (buffer)
   "Run `js2-reparse' if BUFFER is the current buffer, or schedule
 it to be reparsed when the buffer is selected."
-  (if (eq buffer (current-buffer))
-      (js2-reparse)
-    ;; reparse when the buffer is selected again
-    (with-current-buffer buffer
-      (add-hook 'window-configuration-change-hook
-                #'js2-mode-idle-reparse-inner
-                nil t))))
+  (cond ((eq buffer (current-buffer))
+         (js2-reparse))
+        ((buffer-live-p buffer)
+         ;; reparse when the buffer is selected again
+         (with-current-buffer buffer
+           (add-hook 'window-configuration-change-hook
+                     #'js2-mode-idle-reparse-inner
+                     nil t)))))
 
 (defun js2-mode-idle-reparse-inner ()
   (remove-hook 'window-configuration-change-hook
@@ -10570,18 +10578,17 @@ 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.
-Also clears the `js2-magic' bit on autoinserted parens/brackets
-if the edit occurred on a line different from the magic paren."
-  (let* ((magic-pos (next-single-property-change (point-min) 'js2-magic))
-         (line (if magic-pos (line-number-at-pos magic-pos))))
-    (and line
-         (or (/= (line-number-at-pos beg) line)
-             (and (> 0 len)
-                  (/= (line-number-at-pos end) line)))
-         (js2-mode-mundanify-parens)))
+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)
+  "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."
   (setq js2-mode-buffer-dirty-p t)
   (js2-mode-hide-overlay)
   (js2-mode-reset-timer))
@@ -10599,7 +10606,7 @@ buffer will only rebuild its `js2-mode-ast' if the buffer is dirty."
       (unwind-protect
           (when (or js2-mode-buffer-dirty-p force)
             (js2-remove-overlays)
-            (js2-with-unmodifying-text-property-changes
+            (with-silent-modifications
               (setq js2-mode-buffer-dirty-p nil
                     js2-mode-fontifications nil
                     js2-mode-deferred-properties nil)
@@ -10618,7 +10625,6 @@ buffer will only rebuild its `js2-mode-ast' if the buffer is dirty."
                              (js2-mode-remove-suppressed-warnings)
                              (js2-mode-show-warnings)
                              (js2-mode-show-errors)
-                             (js2-mode-highlight-magic-parens)
                              (if (>= js2-highlight-level 1)
                                  (js2-highlight-jsdoc js2-mode-ast))
                              nil))))
@@ -10649,7 +10655,7 @@ buffer will only rebuild its `js2-mode-ast' if the buffer is dirty."
             (move-overlay js2-mode-node-overlay beg end)
           (setq js2-mode-node-overlay (make-overlay beg end))
           (overlay-put js2-mode-node-overlay 'font-lock-face 'highlight))
-        (js2-with-unmodifying-text-property-changes
+        (with-silent-modifications
           (put-text-property beg end 'point-left #'js2-mode-hide-overlay))
         (message "%s, parent: %s"
                  (js2-node-short-name node)
@@ -10657,7 +10663,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
@@ -10667,7 +10673,7 @@ P1 and P2 are the old and new values of point, respectively."
       (unless (and p2
                    (>= p2 beg)
                    (<= p2 end))
-        (js2-with-unmodifying-text-property-changes
+        (with-silent-modifications
           (remove-text-properties beg end '(point-left nil)))
         (delete-overlay js2-mode-node-overlay)
         (setq js2-mode-node-overlay nil)))))
@@ -10678,9 +10684,10 @@ P1 and P2 are the old and new values of point, respectively."
   (js2-mode-exit)
   (js2-mode))
 
-(defsubst js2-mode-show-warn-or-err (e face)
+(defun js2-mode-show-warn-or-err (e face)
   "Highlight a warning or error E with FACE.
-E is a list of ((MSG-KEY MSG-ARG) BEG END)."
+E is a list of ((MSG-KEY MSG-ARG) BEG LEN OVERRIDE-FACE).
+The last element is optional.  When present, use instead of FACE."
   (let* ((key (first e))
          (beg (second e))
          (end (+ beg (third e)))
@@ -10689,7 +10696,7 @@ E is a list of ((MSG-KEY MSG-ARG) BEG END)."
          (end (max (point-min) (min end (point-max))))
          (js2-highlight-level 3)    ; so js2-set-face is sure to fire
          (ovl (make-overlay beg end)))
-    (overlay-put ovl 'font-lock-face face)
+    (overlay-put ovl 'font-lock-face (or (fourth e) face))
     (overlay-put ovl 'js2-error t)
     (put-text-property beg end 'help-echo (js2-get-msg key))
     (put-text-property beg end 'point-entered #'js2-echo-error)))
@@ -10728,7 +10735,7 @@ Defaults to point."
   "Highlight syntax errors."
   (when js2-mode-show-parse-errors
     (dolist (e (js2-ast-root-errors js2-mode-ast))
-      (js2-mode-show-warn-or-err e 'js2-error-face))))
+      (js2-mode-show-warn-or-err e 'js2-error))))
 
 (defun js2-mode-remove-suppressed-warnings ()
   "Take suppressed warnings out of the AST warnings list.
@@ -10762,77 +10769,58 @@ This ensures that the counts and `next-error' are correct."
   "Highlight strict-mode warnings."
   (when js2-mode-show-strict-warnings
     (dolist (e (js2-ast-root-warnings js2-mode-ast))
-      (js2-mode-show-warn-or-err e 'js2-warning-face))))
+      (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)))
-    (if (and msg (or (not (current-message))
-                     (string= (current-message) "Quit")))
-        (message msg))))
+    (when (and (stringp msg)
+               (not (active-minibuffer-window))
+               (not (current-message)))
+      (message msg))))
 
-(defalias #'js2-echo-help #'js2-echo-error)
+(defalias 'js2-echo-help #'js2-echo-error)
 
-(defun js2-enter-key ()
-  "Handle user pressing the Enter key."
+(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."
   (interactive)
-  (let ((parse-status (save-excursion
-                        (syntax-ppss (point))))
-        (js2-bounce-indent-p nil))
+  (let ((parse-status (syntax-ppss)))
     (cond
-     ;; check if we're inside a string
+     ;; Check if we're inside a string.
      ((nth 3 parse-status)
       (if js2-concat-multiline-strings
           (js2-mode-split-string parse-status)
         (insert "\n")))
-     ;; check if inside a block comment
+     ;; Check if inside a block comment.
      ((nth 4 parse-status)
-      (js2-mode-extend-comment))
+      (js2-mode-extend-comment (nth 8 parse-status)))
      (t
-      ;; should probably figure out what the mode-map says we should do
-      (if js2-indent-on-enter-key
-          (js2-indent-line))
-      (insert "\n")
-      (if js2-enter-indents-newline
-          (js2-indent-line))))))
+      (newline-and-indent)))))
 
 (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))
-         (quote-string (string quote-char))
-         (string-beg (nth 8 parse-status))
-         (at-eol (eq js2-concat-multiline-strings 'eol))
-         (indent (save-match-data
-                   (or
-                    (save-excursion
-                      (back-to-indentation)
-                      (if (looking-at "\\+")
-                          (current-column)))
-                    (save-excursion
-                      (goto-char string-beg)
-                      (if (looking-back "\\+\\s-+")
-                          (goto-char (match-beginning 0)))
-                      (current-column))))))
+  (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"))
-    ;; FIXME: This does not match the behavior of `js2-indent-line'.
-    (indent-to indent)
+    (insert (if at-eol " +\n" "\n"))
     (unless at-eol
       (insert "+ "))
-    (insert quote-string)
+    (js2-indent-line)
+    (insert quote-char)
     (when (eolp)
-      (insert quote-string)
+      (insert quote-char)
       (backward-char 1))))
 
-(defun js2-mode-extend-comment ()
+(defun js2-mode-extend-comment (start-pos)
   "Indent the line and, when inside a comment block, add comment prefix."
   (let (star single col first-line needs-close)
     (save-excursion
       (back-to-indentation)
+      (when (< (point) start-pos)
+        (goto-char start-pos))
       (cond
        ((looking-at "\\*[^/]")
         (setq star t
@@ -10861,6 +10849,7 @@ PARSE-STATUS is as documented in `parse-partial-sexp'."
                 (save-excursion
                   (skip-chars-forward " \t\r\n")
                   (not (eq (char-after) ?*))))))
+    (delete-horizontal-space)
     (insert "\n")
     (cond
      (star
@@ -10876,16 +10865,15 @@ PARSE-STATUS is as documented in `parse-partial-sexp'."
               (and (zerop (forward-line 1))
                    (looking-at "\\s-*//"))))
       (indent-to col)
-      (insert "// "))
-     ;; don't need to extend the comment after all
-     (js2-enter-indents-newline
-      (js2-indent-line)))))
+      (insert "// ")))
+    ;; Don't need to extend the comment after all.
+    (js2-indent-line)))
 
 (defun js2-beginning-of-line ()
-  "Toggles point between bol and first non-whitespace char in line.
+  "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))
@@ -10902,240 +10890,12 @@ Also moves past comment delimiters when inside comments."
       (goto-char (point-at-bol))))))
 
 (defun js2-end-of-line ()
-  "Toggles point between eol and last non-whitespace char in line."
+  "Toggle point between eol and last non-whitespace char in line."
   (interactive)
   (if (eolp)
       (skip-chars-backward " \t")
     (goto-char (point-at-eol))))
 
-(defun js2-enter-mirror-mode()
-  "Turns on mirror mode, where quotes, brackets etc are mirrored automatically
-  on insertion."
-  (interactive)
-  (define-key js2-mode-map (read-kbd-macro "{")  'js2-mode-match-curly)
-  (define-key js2-mode-map (read-kbd-macro "}")  'js2-mode-magic-close-paren)
-  (define-key js2-mode-map (read-kbd-macro "\"") 'js2-mode-match-double-quote)
-  (define-key js2-mode-map (read-kbd-macro "'")  'js2-mode-match-single-quote)
-  (define-key js2-mode-map (read-kbd-macro "(")  'js2-mode-match-paren)
-  (define-key js2-mode-map (read-kbd-macro ")")  'js2-mode-magic-close-paren)
-  (define-key js2-mode-map (read-kbd-macro "[")  'js2-mode-match-bracket)
-  (define-key js2-mode-map (read-kbd-macro "]")  'js2-mode-magic-close-paren))
-
-(defun js2-leave-mirror-mode()
-  "Turns off mirror mode."
-  (interactive)
-  (dolist (key '("{" "\"" "'" "(" ")" "[" "]"))
-    (define-key js2-mode-map (read-kbd-macro key) 'self-insert-command)))
-
-(defsubst js2-mode-inside-string ()
-  "Return non-nil if inside a string.
-Actually returns the quote character that begins the string."
-   (let ((parse-state (save-excursion
-                        (syntax-ppss (point)))))
-      (nth 3 parse-state)))
-
-(defsubst js2-mode-inside-comment-or-string ()
-  "Return non-nil if inside a comment or string."
-  (or
-   (let ((comment-start
-          (save-excursion
-            (goto-char (point-at-bol))
-            (if (re-search-forward "//" (point-at-eol) t)
-                (match-beginning 0)))))
-     (and comment-start
-          (<= comment-start (point))))
-   (let ((parse-state (save-excursion
-                        (syntax-ppss (point)))))
-     (or (nth 3 parse-state)
-         (nth 4 parse-state)))))
-
-(defsubst js2-make-magic-delimiter (delim &optional pos)
-  "Add `js2-magic' and `js2-magic-paren-face' to DELIM, a string.
-Sets value of `js2-magic' text property to line number at POS."
-  (propertize delim
-              'js2-magic (line-number-at-pos pos)
-              'font-lock-face 'js2-magic-paren-face))
-
-(defun js2-mode-match-delimiter (open close)
-  "Insert OPEN (a string) and possibly matching delimiter CLOSE.
-The rule we use, which as far as we can tell is how Eclipse works,
-is that we insert the match if we're not in a comment or string,
-and the next non-whitespace character is either punctuation or
-occurs on another line."
-  (insert open)
-  (when (and (looking-at "\\s-*\\([[:punct:]]\\|$\\)")
-             (not (js2-mode-inside-comment-or-string)))
-    (save-excursion
-      (insert (js2-make-magic-delimiter close)))
-    (when js2-auto-indent-p
-      (let ((js2-bounce-indent-p (js2-code-at-bol-p)))
-        (js2-indent-line)))))
-
-(defun js2-mode-match-bracket ()
-  "Insert matching bracket."
-  (interactive)
-  (js2-mode-match-delimiter "[" "]"))
-
-(defun js2-mode-match-paren ()
-  "Insert matching paren unless already inserted."
-  (interactive)
-  (js2-mode-match-delimiter "(" ")"))
-
-(defun js2-mode-match-curly (arg)
-  "Insert matching curly-brace.
-With prefix arg, no formatting or indentation will occur -- the close-brace
-is simply inserted directly at the point."
-  (interactive "p")
-  (let (try-pos)
-    (cond
-     (current-prefix-arg
-      (js2-mode-match-delimiter "{" "}"))
-     ((and js2-auto-insert-catch-block
-           (setq try-pos (if (looking-back "\\s-*\\(try\\)\\s-*"
-                                           (point-at-bol))
-                             (match-beginning 1))))
-      (js2-insert-catch-skel try-pos))
-     (t
-      ;; Otherwise try to do something smarter.
-      (insert "{")
-      (unless (or (not (looking-at "\\s-*$"))
-                  (save-excursion
-                    (skip-chars-forward " \t\r\n")
-                    (and (looking-at "}")
-                         (js2-error-at-point)))
-                  (js2-mode-inside-comment-or-string))
-        (undo-boundary)
-        ;; absolutely mystifying bug:  when inserting the next "\n",
-        ;; the buffer-undo-list is given two new entries:  the inserted range,
-        ;; and the incorrect position of the point.  It's recorded incorrectly
-        ;; as being before the opening "{", not after it.  But it's recorded
-        ;; as the correct value if you're debugging `js2-mode-match-curly'
-        ;; in edebug.  I have no idea why it's doing this, but incrementing
-        ;; the inserted position fixes the problem, so that the undo takes us
-        ;; back to just after the user-inserted "{".
-        (insert "\n")
-        (ignore-errors
-          (incf (cadr buffer-undo-list)))
-        (js2-indent-line)
-        (save-excursion
-          (insert "\n}")
-          (let ((js2-bounce-indent-p (js2-code-at-bol-p)))
-            (js2-indent-line))))))))
-
-(defun js2-insert-catch-skel (try-pos)
-  "Complete a try/catch block after inserting a { following a try keyword.
-Rationale is that a try always needs a catch or a finally, and the catch is
-the more likely of the two.
-
-TRY-POS is the buffer position of the try keyword.  The open-curly should
-already have been inserted."
-  (insert "{")
-  (let ((try-col (save-excursion
-                   (goto-char try-pos)
-                   (current-column))))
-    (insert "\n")
-    (undo-boundary)
-    (js2-indent-line) ;; indent the blank line where cursor will end up
-    (save-excursion
-      (insert "\n")
-      (indent-to try-col)
-      (insert "} catch (x) {\n\n")
-      (indent-to try-col)
-      (insert "}"))))
-
-(defun js2-mode-highlight-magic-parens ()
-  "Re-highlight magic parens after parsing nukes the 'face prop."
-  (let ((beg (point-min))
-        end)
-    (while (setq beg (next-single-property-change beg 'js2-magic))
-      (setq end (next-single-property-change (1+ beg) 'js2-magic))
-      (if (get-text-property beg 'js2-magic)
-          (js2-with-unmodifying-text-property-changes
-            (put-text-property beg (or end (1+ beg))
-                               'font-lock-face 'js2-magic-paren-face))))))
-
-(defun js2-mode-mundanify-parens ()
-  "Clear all magic parens and brackets."
-  (let ((beg (point-min))
-        end)
-    (while (setq beg (next-single-property-change beg 'js2-magic))
-      (setq end (next-single-property-change (1+ beg) 'js2-magic))
-      (remove-text-properties beg (or end (1+ beg))
-                              '(js2-magic face)))))
-
-(defsubst js2-match-quote (quote-string)
-  (let ((start-quote (js2-mode-inside-string)))
-    (cond
-     ;; inside a comment - don't do quote-matching, since we can't
-     ;; reliably figure out if we're in a string inside the comment
-     ((js2-comment-at-point)
-      (insert quote-string))
-     ((not start-quote)
-      ;; not in string => insert matched quotes
-      (insert quote-string)
-      ;; exception:  if we're just before a word, don't double it.
-      (unless (looking-at "[^ \t\r\n]")
-        (save-excursion
-          (insert quote-string))))
-     ((looking-at quote-string)
-      (if (looking-back "[^\\]\\\\")
-          (insert quote-string)
-        (forward-char 1)))
-     ((and js2-mode-escape-quotes
-           (save-excursion
-             (save-match-data
-               (re-search-forward quote-string (point-at-eol) t))))
-      ;; inside terminated string, escape quote (unless already escaped)
-      (insert (if (looking-back "[^\\]\\\\")
-                  quote-string
-                (concat "\\" quote-string))))
-     (t
-      (insert quote-string)))))        ; else terminate the string
-
-(defun js2-mode-match-single-quote ()
-  "Insert matching single-quote."
-  (interactive)
-  (let ((parse-status (syntax-ppss (point))))
-    ;; don't match inside comments, since apostrophe is more common
-    (if (nth 4 parse-status)
-        (insert "'")
-      (js2-match-quote "'"))))
-
-(defun js2-mode-match-double-quote ()
-  "Insert matching double-quote."
-  (interactive)
-  (js2-match-quote "\""))
-
-;; Eclipse works as follows:
-;;  * type an open-paren and it auto-inserts close-paren
-;;    - auto-inserted paren gets a green bracket
-;;    - green bracket means typing close-paren there will skip it
-;;  * if you insert any text on a different line, it turns off
-(defun js2-mode-magic-close-paren ()
-  "Skip over close-paren rather than inserting, where appropriate."
-  (interactive)
-  (let* ((here (point))
-         (parse-status (syntax-ppss here))
-         (open-pos (nth 1 parse-status))
-         (close last-input-event)
-         (open (cond
-                ((eq close ?\))
-                 ?\()
-                ((eq close ?\])
-                 ?\[)
-                ((eq close ?})
-                 ?{)
-                (t nil))))
-    (if (and (eq (char-after) close)
-             (eq open (char-after open-pos))
-             (js2-same-line open-pos)
-             (get-text-property here 'js2-magic))
-        (progn
-          (remove-text-properties here (1+ here) '(js2-magic face))
-          (forward-char 1))
-      (insert-char close 1))
-    (blink-matching-open)))
-
 (defun js2-mode-wait-for-parse (callback)
   "Invoke CALLBACK when parsing is finished.
 If parsing is already finished, calls CALLBACK immediately."
@@ -11171,7 +10931,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))
 
@@ -11202,31 +10962,30 @@ Returns nil if point is not in a function."
   (interactive)
   (let (comment fn pos)
     (save-excursion
-      (save-match-data
-        (cond
-         ;; /* ... */ comment?
-         ((js2-block-comment-p (setq comment (js2-comment-at-point)))
-          (if (js2-mode-invisible-overlay-bounds
-               (setq pos (+ 3 (js2-node-abs-pos comment))))
-              (progn
-                (goto-char pos)
-                (js2-mode-show-element))
-            (js2-mode-hide-element)))
-         ;; //-comment?
-         ((save-excursion
-            (back-to-indentation)
-            (looking-at js2-mode-//-comment-re))
-          (js2-mode-toggle-//-comment))
-         ;; function?
-         ((setq fn (js2-mode-function-at-point))
-          (setq pos (and (js2-function-node-body fn)
-                         (js2-node-abs-pos (js2-function-node-body fn))))
-          (goto-char (1+ pos))
-          (if (js2-mode-invisible-overlay-bounds)
-              (js2-mode-show-element)
-            (js2-mode-hide-element)))
-         (t
-          (message "Nothing at point to hide or show")))))))
+      (cond
+       ;; /* ... */ comment?
+       ((js2-block-comment-p (setq comment (js2-comment-at-point)))
+        (if (js2-mode-invisible-overlay-bounds
+             (setq pos (+ 3 (js2-node-abs-pos comment))))
+            (progn
+              (goto-char pos)
+              (js2-mode-show-element))
+          (js2-mode-hide-element)))
+       ;; //-comment?
+       ((save-excursion
+          (back-to-indentation)
+          (looking-at js2-mode-//-comment-re))
+        (js2-mode-toggle-//-comment))
+       ;; function?
+       ((setq fn (js2-mode-function-at-point))
+        (setq pos (and (js2-function-node-body fn)
+                       (js2-node-abs-pos (js2-function-node-body fn))))
+        (goto-char (1+ pos))
+        (if (js2-mode-invisible-overlay-bounds)
+            (js2-mode-show-element)
+          (js2-mode-hide-element)))
+       (t
+        (message "Nothing at point to hide or show"))))))
 
 (defun js2-mode-hide-element ()
   "Fold/hide contents of a block, showing ellipses.
@@ -11338,12 +11097,11 @@ 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)))
 
-(defsubst js2-mode-extend-//-comment (direction)
+(defun js2-mode-extend-//-comment (direction)
   "Find start or end of a block of similar //-comment lines.
 DIRECTION is -1 to look back, 1 to look forward.
 INDENT is the indentation level to match.
@@ -11353,27 +11111,25 @@ If there is no such matching line, returns current end of line."
   (let ((pos (point-at-eol))
         (indent (current-indentation)))
     (save-excursion
-      (save-match-data
-        (while (and (zerop (forward-line direction))
-                    (looking-at js2-mode-//-comment-re)
-                    (eq indent (length (match-string 1))))
-          (setq pos (point-at-eol)))
+      (while (and (zerop (forward-line direction))
+                  (looking-at js2-mode-//-comment-re)
+                  (eq indent (length (match-string 1))))
+        (setq pos (point-at-eol))
       pos))))
 
 (defun js2-mode-hide-//-comments ()
   "Fold adjacent 1-line comments, showing only snippet of first one."
   (let (beg end)
     (save-excursion
-      (save-match-data
-        (goto-char (point-min))
-        (while (re-search-forward js2-mode-//-comment-re nil t)
-          (setq beg (point)
-                end (js2-mode-extend-//-comment 1))
-          (unless (eq beg end)
-            (overlay-put (js2-mode-flag-region beg end 'hide)
-                         'comment t))
-          (goto-char end)
-          (forward-char 1))))))
+      (goto-char (point-min))
+      (while (re-search-forward js2-mode-//-comment-re nil t)
+        (setq beg (point)
+              end (js2-mode-extend-//-comment 1))
+        (unless (eq beg end)
+          (overlay-put (js2-mode-flag-region beg end 'hide)
+                       'comment t))
+        (goto-char end)
+        (forward-char 1)))))
 
 (defun js2-mode-toggle-//-comment ()
   "Fold or un-fold any multi-line //-comment at point.
@@ -11423,7 +11179,7 @@ Some users don't like having warnings/errors reported while they type."
   (interactive)
   (setq js2-mode-show-parse-errors (not js2-mode-show-parse-errors)
         js2-mode-show-strict-warnings (not js2-mode-show-strict-warnings))
-  (if (interactive-p)
+  (if (called-interactively-p 'any)
       (message "warnings and errors %s"
                (if js2-mode-show-parse-errors
                    "enabled"
@@ -11442,66 +11198,71 @@ move backward across N balanced expressions."
   (setq arg (or arg 1))
   (save-restriction
     (widen) ;; `blink-matching-open' calls `narrow-to-region'
-    (js2-reparse))
-  (let ((scan-msg "Containing expression ends prematurely")
-        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 (i (- 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))))
-        (goto-char
-         (or (when (and lp (> start lp))
-               (if (and rp (<= start rp))
-                   (if (setq child (js2-node-closest-child node (point) lp t))
-                       (js2-node-abs-pos child)
-                     (goto-char start)
-                     (signal 'scan-error (list scan-msg lp lp)))
-                 lp))
-             pos
-             (point-min)))))
-     (t
-      ;; forward-sexp
-      (js2-forward-sws)
-      (dotimes (i 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))))
-        (goto-char
-         (or (when (and rp (<= start rp))
+    (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)
-                   (if (setq child (js2-node-closest-child node (point) rp))
-                       (js2-node-abs-end child)
-                     (goto-char start)
-                     (signal 'scan-error (list scan-msg rp (1+ rp))))
-                 (1+ rp)))
-             (and pos
-                  (+ pos
-                     (js2-node-len
-                      (if (js2-expr-stmt-node-p (js2-node-parent node))
-                          ;; stop after the semicolon
-                          (js2-node-parent node)
-                        node))))
-             (point-max))))))))
+                   (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."
   (cond
    ((or (js2-array-node-p node)
         (js2-object-node-p node)
-        (js2-array-comp-node-p node)
+        (js2-comp-node-p node)
         (memq (aref node 0) '(cl-struct-js2-block-node cl-struct-js2-scope)))
     (cons abs-pos (+ abs-pos (js2-node-len node) -1)))
    ((js2-paren-expr-node-p node)
@@ -11520,7 +11281,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.
@@ -11533,21 +11294,39 @@ move backward across N balanced expressions."
            nil))))
     found))
 
+(defun js2-errors ()
+  "Return a list of errors found."
+  (and js2-mode-ast
+       (js2-ast-root-errors js2-mode-ast)))
+
+(defun js2-warnings ()
+  "Return a list of warnings found."
+  (and js2-mode-ast
+       (js2-ast-root-warnings js2-mode-ast)))
+
+(defun js2-have-errors-p ()
+  "Return non-nil if any parse errors or warnings were found."
+  (or (js2-errors) (js2-warnings)))
+
+(defun js2-errors-and-warnings ()
+  "Return a copy of the concatenated errors and warnings lists.
+They are appended:  first the errors, then the warnings.
+Entries are of the form (MSG BEG END)."
+  (when js2-mode-ast
+    (append (js2-ast-root-errors js2-mode-ast)
+            (copy-sequence (js2-ast-root-warnings js2-mode-ast)))))
+
 (defun js2-next-error (&optional arg reset)
   "Move to next parse error.
 Typically invoked via \\[next-error].
 ARG is the number of errors, forward or backward, to move.
 RESET means start over from the beginning."
   (interactive "p")
-  (if (or (null js2-mode-ast)
-          (and (null (js2-ast-root-errors js2-mode-ast))
-               (null (js2-ast-root-warnings js2-mode-ast))))
+  (if (not (or (js2-errors) (js2-warnings)))
       (message "No errors")
     (when reset
       (goto-char (point-min)))
-    (let* ((errs (copy-sequence
-                  (append (js2-ast-root-errors js2-mode-ast)
-                          (js2-ast-root-warnings js2-mode-ast))))
+    (let* ((errs (js2-errors-and-warnings))
            (continue t)
            (start (point))
            (count (or arg 1))
@@ -11555,24 +11334,25 @@ RESET means start over from the beginning."
            (sorter (if backward '> '<))
            (stopper (if backward '< '>))
            (count (abs count))
-           all-errs
-           err)
-      ;; sort by start position
+           all-errs err)
+      ;; Sort by start position.
       (setq errs (sort errs (lambda (e1 e2)
                               (funcall sorter (second e1) (second e2))))
             all-errs errs)
-      ;; find nth error with pos > start
+      ;; Find nth error with pos > start.
       (while (and errs continue)
         (when (funcall stopper (cadar errs) start)
           (setq err (car errs))
           (if (zerop (decf count))
               (setq continue nil)))
         (setq errs (cdr errs)))
+      ;; Clear for `js2-echo-error'.
+      (message nil)
       (if err
           (goto-char (second err))
-        ;; wrap around to first error
+        ;; Wrap around to first error.
         (goto-char (second (car all-errs)))
-        ;; if we were already on it, echo msg again
+        ;; If we were already on it, echo msg again.
         (if (= (point) start)
             (js2-echo-error (point) (point)))))))
 
@@ -11606,8 +11386,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))
@@ -11652,9 +11432,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
@@ -11664,9 +11442,9 @@ or script-level element."
                      (js2-node-parent-script-or-fn node)))
          script)
     (unless (js2-function-node-p parent)
-      ;; use current script-level node, or, if none, the next one
-      (setq script (or parent node))
-      (setq parent (js2-node-find-child-before (point) script))
+      ;; Use current script-level node, or, if none, the next one.
+      (setq script (or parent node)
+            parent (js2-node-find-child-before (point) script))
       (when (or (null parent)
                 (>= (point) (+ (js2-node-abs-pos parent)
                                (js2-node-len parent))))
@@ -11690,8 +11468,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)