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

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

[ELPA-diffs] /srv/bzr/emacs/elpa r328: * js2-mode: merge from upstream


From: Dmitry Gutov
Subject: [ELPA-diffs] /srv/bzr/emacs/elpa r328: * js2-mode: merge from upstream
Date: Tue, 25 Dec 2012 04:17:58 +0400
User-agent: Bazaar (2.5.0)

------------------------------------------------------------
revno: 328
committer: Dmitry Gutov <address@hidden>
branch nick: elpa
timestamp: Tue 2012-12-25 04:17:58 +0400
message:
  * js2-mode: merge from upstream
  
  Lots of small changes, too many to enumerate.
modified:
  packages/js2-mode/js2-mode.el
=== modified file 'packages/js2-mode/js2-mode.el'
--- a/packages/js2-mode/js2-mode.el     2012-10-31 17:07:10 +0000
+++ b/packages/js2-mode/js2-mode.el     2012-12-25 00:17:58 +0000
@@ -1,10 +1,13 @@
 ;;; js2-mode.el --- Improved JavaScript editing mode
 
-;; Copyright (C) 2009, 2011  Free Software Foundation, Inc.
+;; Copyright (C) 2009, 2011, 2012  Free Software Foundation, Inc.
 
-;; Author:  Steve Yegge <address@hidden>
-;; Version:  20090814
-;; Keywords:  languages, javascript
+;; Author: Steve Yegge <address@hidden>
+;;         mooz <address@hidden>
+;;         Dmitry Gutov <address@hidden>
+;; Version: 1.1
+;; Keywords: languages, javascript
+;; Package-Requires: ((emacs "24.1"))
 
 ;; This file is part of GNU Emacs.
 
@@ -26,7 +29,7 @@
 ;; This JavaScript editing mode supports:
 
 ;;  - strict recognition of the Ecma-262 language standard
-;;  - support for most Rhino and SpiderMonkey extensions from 1.5 to 1.8
+;;  - support for most Rhino and SpiderMonkey extensions from 1.5 and up
 ;;  - parsing support for ECMAScript for XML (E4X, ECMA-357)
 ;;  - accurate syntax highlighting using a recursive-descent parser
 ;;  - on-the-fly reporting of syntax errors and strict-mode warnings
@@ -38,9 +41,19 @@
 ;;    - 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))
+
+;; Alternately, 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)
+
 ;; To customize how it works:
 ;;   M-x customize-group RET js2-mode RET
 
@@ -70,14 +83,15 @@
 
 (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
   (mapcar 'symbol-name
-          '(Array Boolean Date Error EvalError Function Infinity
+          '(Array Boolean Date Error EvalError Function Infinity JSON
           Math NaN Number Object RangeError ReferenceError RegExp
           String SyntaxError TypeError URIError arguments
           decodeURI decodeURIComponent encodeURI
@@ -124,7 +138,7 @@
             UserDataHandler
 
             ;; Window
-            alert confirm document java navigator prompt screen
+            window alert confirm document java navigator prompt screen
             self top
 
             ;; W3C CSS
@@ -136,7 +150,7 @@
             DocumentCSS DocumentStyle ElementCSSInlineStyle
             LinkStyle MediaList RGBColor Rect StyleSheet
             StyleSheetList ViewCSS
-            
+
             ;; W3C Event
             EventListener EventTarget Event DocumentEvent UIEvent
             MouseEvent MutationEvent KeyboardEvent
@@ -153,7 +167,7 @@
 (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))
@@ -170,20 +184,24 @@
 
 ;;; Variables
 
-(defvar js2-emacs22 (>= emacs-major-version 22))
+(defun js2-mark-safe-local (name pred)
+  "Make the variable NAME buffer-local and mark it as safe file-local
+variable with predicate PRED."
+  (make-variable-buffer-local name)
+  (put name 'safe-local-variable pred))
 
 (defcustom js2-highlight-level 2
   "Amount of syntax highlighting to perform.
-nil, zero or negative means none.
+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."
   :group 'js2-mode
-  :type '(choice (const :tag "None" nil)
+  :type '(choice (const :tag "None" 0)
                  (const :tag "Basic" 1)
                  (const :tag "Include Properties" 2)
                  (const :tag "Include Functions" 3)))
-  
+
 (defvar js2-mode-dev-mode-p nil
   "Non-nil if running in development mode.  Normally nil.")
 
@@ -199,7 +217,8 @@
 Similar to `c-basic-offset'."
   :group 'js2-mode
   :type 'integer)
-(make-variable-buffer-local 'js2-basic-offset)
+(js2-mark-safe-local 'js2-basic-offset 'integerp)
+
 
 (defcustom js2-bounce-indent-p nil
   "Non-nil to have indent-line function choose among alternatives.
@@ -208,10 +227,30 @@
 already indented to that predetermined column, indenting will choose
 another likely column and indent to that spot.  Repeated invocation of
 the indent-line function will cycle among the computed alternatives.
-See the function `js2-bounce-indent' for details."
+See the function `js2-bounce-indent' for details.  When it is non-nil,
+js2-mode also binds `js2-bounce-indent-backwards' to Shift-Tab."
   :type 'boolean
   :group 'js2-mode)
 
+(defcustom js2-pretty-multiline-declarations t
+  "Non-nil to line up multiline declarations vertically:
+
+  var a = 10,
+      b = 20,
+      c = 30;
+
+If the value is not `all', and the first assigned value in
+declaration is a function/array/object literal spanning several
+lines, it won't be indented additionally:
+
+  var o = {                   var bar = 2,
+    foo: 3          vs.           o = {
+  },                                foo: 3
+      bar = 2;                    };"
+  :group 'js2-mode
+  :type 'symbol)
+(js2-mark-safe-local 'js2-pretty-multiline-declarations 'symbolp)
+
 (defcustom js2-idle-timer-delay 0.2
   "Delay in secs before re-parsing after user makes changes.
 Multiplied by `js2-dynamic-idle-timer-adjust', which see."
@@ -232,15 +271,11 @@
   :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-mode-squeeze-spaces t
-  "Non-nil to normalize whitespace when filling in comments.
-Multiple runs of spaces are converted to a single space."
-  :type 'boolean
+(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
+end of the line, otherwise, at the beginning of the next line."
+  :type '(choice (const t) (const eol) (const nil))
   :group 'js2-mode)
 
 (defcustom js2-mode-show-parse-errors t
@@ -294,14 +329,6 @@
   :type 'boolean
   :group 'js2-mode)
 
-(defcustom js2-strict-cond-assign-warning t
-  "Non-nil to warn about expressions like if (a = b).
-This often should have been '==' instead of '='.  If the warning
-is enabled, you can suppress it on a per-expression basis by
-parenthesizing the expression, e.g. if ((a = b)) ..."
-  :type 'boolean
-  :group 'js2-mode)
-
 (defcustom js2-strict-var-redeclaration-warning t
   "Non-nil to warn about redeclaring variables in a script or function."
   :type 'boolean
@@ -318,12 +345,12 @@
   :type 'boolean
   :group 'js2-mode)
 
-(defcustom js2-language-version 180
+(defcustom js2-language-version 200
   "Configures what JavaScript language version to recognize.
-Currently versions 150, 160, 170 and 180 are supported, corresponding
-to JavaScript 1.5, 1.6, 1.7 and 1.8, respectively.  In a nutshell,
-1.6 adds E4X support, 1.7 adds let, yield, and Array comprehensions,
-and 1.8 adds function closures."
+Currently versions 150, 160, 170, 180 and 200 are supported,
+corresponding to JavaScript 1.5, 1.6, 1.7, 1.8 and 2.0 (Harmony),
+respectively.  In a nutshell, 1.6 adds E4X support, 1.7 adds let,
+yield, and Array comprehensions, and 1.8 adds function closures."
   :type 'integer
   :group 'js2-mode)
 
@@ -344,11 +371,6 @@
   :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,
@@ -406,9 +428,6 @@
   :type 'boolean
   :group 'js2-mode)
 
-(defvar js2-mode-version 20090727
-  "Release number for `js2-mode'.")
-
 ;; scanner variables
 
 (defmacro js2-deflocal (name value &optional comment)
@@ -611,8 +630,9 @@
 
 (defvar js2-COMMENT 160)
 (defvar js2-ENUM 161)  ; for "enum" reserved word
+(defvar js2-TRIPLEDOT 162) ; for rest parameter
 
-(defconst js2-num-tokens (1+ js2-ENUM))
+(defconst js2-num-tokens (1+ js2-TRIPLEDOT))
 
 (defconst js2-debug-print-trees nil)
 
@@ -750,8 +770,11 @@
 
 (js2-deflocal js2-nesting-of-function 0)
 
-(js2-deflocal js2-recorded-assignments nil
-  "Tracks assignments found during parsing.")
+(js2-deflocal js2-recorded-identifiers nil
+  "Tracks identifiers found during parsing.")
+
+(js2-deflocal js2-is-in-destructuring nil
+  "True while parsing destructuring expression.")
 
 (defcustom js2-global-externs nil
   "A list of any extern names you'd like to consider always declared.
@@ -806,7 +829,7 @@
 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.
 
@@ -1028,60 +1051,42 @@
   :group 'js2-mode)
 
 (defface js2-jsdoc-html-tag-name
-  (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"))))
+  '((((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
-  (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"))))
+  '((((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
-  '((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-post-parse-callbacks nil
   "A list of callback functions invoked after parsing finishes.
 Currently, the main use for this function is to add synthetic
-declarations to `js2-recorded-assignments', which see."
+declarations to `js2-recorded-identifiers', which see."
   :type 'list
   :group 'js2-mode)
 
-(defface js2-external-variable
-  '((t :foreground "orange"))
-  "Face used to highlight assignments to undeclared variables.
+(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 assign
-to such a variable, then you are either expecting it to originate from
+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 higlight assignments to undeclared variables."
   :type 'boolean
   :group 'js2-mode)
 
@@ -1094,6 +1099,7 @@
   (let ((map (make-sparse-keymap))
         keys)
     (define-key map [mouse-1] #'js2-mode-show-node)
+    (define-key map (kbd "M-j") #'js2-line-break)
     (define-key map (kbd "C-c C-e") #'js2-mode-hide-element)
     (define-key map (kbd "C-c C-s") #'js2-mode-show-element)
     (define-key map (kbd "C-c C-a") #'js2-mode-show-all)
@@ -1101,10 +1107,9 @@
     (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)
-    ;; FIXME: Why isn't it sufficient to set {beginning,end}-of-defun-function?
-    (define-key map [remap mark-defun] #'js2-mark-defun)
-    (define-key map [remap narrow-to-defun] #'js2-narrow-to-defun)
     (define-key map [down-mouse-3] #'js2-down-mouse-3)
+    (when js2-bounce-indent-p
+      (define-key map (kbd "<backtab>") #'js2-indent-bounce-backwards))
 
     (define-key map [menu-bar javascript]
       (cons "JavaScript" (make-sparse-keymap "JavaScript")))
@@ -1249,7 +1254,7 @@
 
 ;; 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)
@@ -1326,7 +1331,7 @@
 (js2-msg "msg.bad.for.in.destruct"
          "Left hand side of for..in loop must be an array of "
          "length 2 to accept key/value pair.")
-    
+
 (js2-msg "msg.cant.convert"
          "Can't convert to type '%s'.")
 
@@ -1354,11 +1359,11 @@
           "Calling eval() with anything other than a primitive "
           "string value will simply return the value. "
           "Is this what you intended?")
-          
+
 (js2-msg "msg.eval.nonstring.strict"
          "Calling eval() with anything other than a primitive "
          "string value is not allowed in strict mode.")
-    
+
 (js2-msg "msg.bad.destruct.op"
          "Invalid destructuring assignment operator")
 
@@ -1426,7 +1431,7 @@
 (js2-msg "msg.bad.regexp.compile"
          "Only one argument may be specified if the first "
          "argument to RegExp.prototype.compile is a RegExp object.")
-    
+
 ;; Parser
 (js2-msg "msg.got.syntax.errors"
          "Compilation produced %s syntax errors.")
@@ -1436,10 +1441,10 @@
 
 (js2-msg "msg.const.redecl"
          "TypeError: redeclaration of const %s.")
-    
+
 (js2-msg "msg.let.redecl"
          "TypeError: redeclaration of variable %s.")
-    
+
 (js2-msg "msg.parm.redecl"
          "TypeError: redeclaration of formal parameter %s.")
 
@@ -1478,6 +1483,12 @@
 (js2-msg "msg.no.paren.after.parms"
          "missing ) after formal parameters")
 
+(js2-msg "msg.no.default.after.default.param" ; added by js2-mode
+         "parameter without default follows parameter with default")
+
+(js2-msg "msg.param.after.rest" ; added by js2-mode
+         "parameter after rest parameter")
+
 (js2-msg "msg.no.brace.body"
          "missing '{' before function body")
 
@@ -1540,9 +1551,9 @@
 
 (js2-msg "msg.no.semi.for.cond"
          "missing ; after for-loop condition")
-    
+
 (js2-msg "msg.in.after.for.name"
-         "missing in after for")
+         "missing in or of after for")
 
 (js2-msg "msg.no.paren.for.ctrl"
          "missing ) after for-loop control")
@@ -1681,6 +1692,9 @@
 (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'")
 
@@ -1748,7 +1762,7 @@
 (js2-msg "msg.no.ref.from.function"
          "Function %s can not be used as the left-hand "
          "side of assignment or as an operand of ++ or -- operator.")
-    
+
 (js2-msg "msg.bad.default.value"
          "Object's getDefaultValue() method returned an object.")
 
@@ -1771,14 +1785,14 @@
 (js2-msg "msg.ctor.multiple.parms"
          "Can't define constructor or class %s since more than "
          "one constructor has multiple parameters.")
-    
+
 (js2-msg "msg.extend.scriptable"
          "%s must extend ScriptableObject in order to define property %s.")
 
 (js2-msg "msg.bad.getter.parms"
          "In order to define a property, getter %s must have zero "
          "parameters or a single ScriptableObject parameter.")
-    
+
 (js2-msg "msg.obj.getter.parms"
          "Expected static or delegated getter %s to take "
          "a ScriptableObject parameter.")
@@ -1883,7 +1897,7 @@
 
 (js2-msg "msg.already.exec.gen"
          "Already executing generator")
-    
+
 (js2-msg "msg.StopIteration.invalid"
          "StopIteration may not be changed to an arbitrary object.")
 
@@ -1929,8 +1943,8 @@
            ,delta)
        ,form
        (/ (truncate (* (- (float-time (current-time))
-                          (float-time ,beg)))
-                    10000)
+                          (float-time ,beg))
+                       10000))
           10000.0))))
 
 (defsubst js2-same-line (pos)
@@ -1938,7 +1952,7 @@
   (and (>= pos (point-at-bol))
        (<= pos (point-at-eol))))
 
-(defsubst js2-same-line-2 (p1 p2)
+(defun js2-same-line-2 (p1 p2)
   "Return t if P1 is on the same line as P2."
   (save-excursion
     (goto-char p1)
@@ -1948,15 +1962,19 @@
   "Signal an error when we encounter an unexpected code path."
   (error "failed assertion"))
 
+(defsubst js2-record-text-property (beg end prop value)
+  "Record a text property to set when parsing finishes."
+  (push (list beg end prop value) js2-mode-deferred-properties))
+
 ;; 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)))
         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)
@@ -1968,15 +1986,16 @@
                   (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 len (- js2-token-end js2-token-beg))
+                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)))))
@@ -2035,12 +2054,12 @@
 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
@@ -2053,23 +2072,24 @@
 
 The node traversal is approximately lexical-order, although there
 are currently no guarantees around this."
-  (let ((vfunc (get (aref node 0) 'js2-visitor)))
-    ;; visit the node
-    (when  (funcall callback node nil)
-      ;; visit the kids
-      (cond
-       ((eq vfunc 'js2-visit-none)
-        nil)                            ; don't even bother calling it
-       ;; Each AST node type has to define a `js2-visitor' function
-       ;; that takes a node and a callback, and calls `js2-visit-ast'
-       ;; on each child of the node.
-       (vfunc
-        (funcall vfunc node callback))
-       (t
-        (error "%s does not define a visitor-traversal function"
-               (aref node 0)))))
-    ;; call the end-visit
-    (funcall callback node t)))
+  (when node
+    (let ((vfunc (get (aref node 0) 'js2-visitor)))
+      ;; visit the node
+      (when  (funcall callback node nil)
+        ;; visit the kids
+        (cond
+         ((eq vfunc 'js2-visit-none)
+          nil)                            ; don't even bother calling it
+         ;; Each AST node type has to define a `js2-visitor' function
+         ;; that takes a node and a callback, and calls `js2-visit-ast'
+         ;; on each child of the node.
+         (vfunc
+          (funcall vfunc node callback))
+         (t
+          (error "%s does not define a visitor-traversal function"
+                 (aref node 0)))))
+      ;; call the end-visit
+      (funcall callback node t))))
 
 (defstruct (js2-node
             (:constructor nil))  ; abstract
@@ -2087,7 +2107,7 @@
   (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)
@@ -2095,7 +2115,7 @@
       (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."
@@ -2105,7 +2125,7 @@
          (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))))
@@ -2115,7 +2135,7 @@
   "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.
@@ -2129,12 +2149,12 @@
                                                     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)))
@@ -2163,7 +2183,7 @@
   parent-scope  ; a `js2-scope'
   top)          ; top-level `js2-scope' (script/function)
 
-(put 'cl-struct-js2-scope 'js2-visitor 'js2-visit-none)
+(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)
@@ -2197,7 +2217,7 @@
         (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)
@@ -2206,7 +2226,7 @@
                     (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'."
   (let* ((table (js2-scope-symbol-table scope))
@@ -2246,8 +2266,8 @@
                                                      len
                                                      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
@@ -2270,9 +2290,9 @@
                                                   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)
@@ -2313,7 +2333,7 @@
   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)
@@ -2370,10 +2390,8 @@
             (:constructor nil)
             (:constructor make-js2-while-node (&key (type js2-WHILE)
                                                     (pos js2-token-beg)
-                                                    len
-                                                    body
-                                                    condition
-                                                    lp
+                                                    len body
+                                                    condition lp
                                                     rp)))
   "AST node for while-loop."
   condition)    ; while-condition
@@ -2398,13 +2416,9 @@
             (: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
@@ -2436,21 +2450,20 @@
             (:constructor nil)
             (:constructor make-js2-for-in-node (&key (type js2-FOR)
                                                      (pos js2-ts-cursor)
-                                                     len
-                                                     body
+                                                     len body
                                                      iterator
                                                      object
                                                      in-pos
                                                      each-pos
-                                                     foreach-p
-                                                     lp
-                                                     rp)))
+                                                     foreach-p forof-p
+                                                     lp rp)))
   "AST node for a for..in loop."
   iterator  ; [var] foo in ...
   object    ; object over which we're iterating
   in-pos    ; buffer position of 'in' keyword
   each-pos  ; buffer position of 'each' keyword, if foreach-p
-  foreach-p) ; t if it's a for-each loop
+  foreach-p ; t if it's a for-each loop
+  forof-p)  ; t if it's a for-of loop
 
 (put 'cl-struct-js2-for-in-node 'js2-visitor 'js2-visit-for-in-node)
 (put 'cl-struct-js2-for-in-node 'js2-printer 'js2-print-for-in-node)
@@ -2462,13 +2475,16 @@
 
 (defun js2-print-for-in-node (n i)
   (let ((pad (js2-make-pad i))
-        (foreach (js2-for-in-node-foreach-p n)))
+        (foreach (js2-for-in-node-foreach-p n))
+        (forof (js2-for-in-node-forof-p n)))
     (insert pad "for ")
     (if foreach
         (insert "each "))
     (insert "(")
     (js2-print-ast (js2-for-in-node-iterator n) 0)
-    (insert " in ")
+    (if forof
+        (insert " of ")
+      (insert " in "))
     (js2-print-ast (js2-for-in-node-object n) 0)
     (insert ") {\n")
     (js2-print-body (js2-for-in-node-body n) (1+ i))
@@ -2488,8 +2504,7 @@
 (put 'cl-struct-js2-return-node 'js2-printer 'js2-print-return-node)
 
 (defun js2-visit-return-node (n v)
-  (if (js2-return-node-retval n)
-      (js2-visit-ast (js2-return-node-retval n) v)))
+  (js2-visit-ast (js2-return-node-retval n) v))
 
 (defun js2-print-return-node (n i)
   (insert (js2-make-pad i) "return")
@@ -2503,12 +2518,10 @@
             (: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
@@ -2524,8 +2537,7 @@
 (defun js2-visit-if-node (n v)
   (js2-visit-ast (js2-if-node-condition n) v)
   (js2-visit-ast (js2-if-node-then-part n) v)
-  (if (js2-if-node-else-part n)
-      (js2-visit-ast (js2-if-node-else-part n) v)))
+  (js2-visit-ast (js2-if-node-else-part n) v))
 
 (defun js2-print-if-node (n i)
   (let ((pad (js2-make-pad i))
@@ -2558,7 +2570,7 @@
                                                   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)
@@ -2568,8 +2580,7 @@
   (js2-visit-ast (js2-try-node-try-block n) v)
   (dolist (clause (js2-try-node-catch-clauses n))
     (js2-visit-ast clause v))
-  (if (js2-try-node-finally-block n)
-      (js2-visit-ast (js2-try-node-finally-block n) v)))
+  (js2-visit-ast (js2-try-node-finally-block n) v))
 
 (defun js2-print-try-node (n i)
   (let ((pad (js2-make-pad i))
@@ -2591,14 +2602,13 @@
             (:constructor make-js2-catch-node (&key (type js2-CATCH)
                                                     (pos js2-ts-cursor)
                                                     len
-                                                    var-name
+                                                    param
                                                     guard-kwd
                                                     guard-expr
-                                                    block
-                                                    lp
+                                                    block lp
                                                     rp)))
   "AST node for a catch clause."
-  var-name    ; a `js2-name-node'
+  param       ; destructuring form or simple name node
   guard-kwd   ; relative buffer position of "if" in "catch (x if ...)"
   guard-expr  ; catch condition, a `js2-node'
   block       ; statements, a `js2-block-node'
@@ -2609,7 +2619,7 @@
 (put 'cl-struct-js2-catch-node 'js2-printer 'js2-print-catch-node)
 
 (defun js2-visit-catch-node (n v)
-  (js2-visit-ast (js2-catch-node-var-name n) v)
+  (js2-visit-ast (js2-catch-node-param n) v)
   (when (js2-catch-node-guard-kwd n)
     (js2-visit-ast (js2-catch-node-guard-expr n) v))
   (js2-visit-ast (js2-catch-node-block n) v))
@@ -2619,7 +2629,7 @@
         (guard-kwd (js2-catch-node-guard-kwd n))
         (guard-expr (js2-catch-node-guard-expr n)))
     (insert " catch (")
-    (js2-print-ast (js2-catch-node-var-name n) 0)
+    (js2-print-ast (js2-catch-node-param n) 0)
     (when guard-kwd
       (insert " if ")
       (js2-print-ast guard-expr 0))
@@ -2632,8 +2642,7 @@
             (: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
 
@@ -2656,12 +2665,11 @@
                                                      (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
 
@@ -2688,9 +2696,7 @@
             (: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)
 
@@ -2698,8 +2704,7 @@
 (put 'cl-struct-js2-case-node 'js2-printer 'js2-print-case-node)
 
 (defun js2-visit-case-node (n v)
-  (if (js2-case-node-expr n)  ; nil for default: case
-      (js2-visit-ast (js2-case-node-expr n) v))
+  (js2-visit-ast (js2-case-node-expr n) v)
   (js2-visit-block n v))
 
 (defun js2-print-case-node (n i)
@@ -2719,8 +2724,7 @@
             (: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
 
@@ -2740,11 +2744,8 @@
             (: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
@@ -2771,8 +2772,7 @@
             (: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
@@ -2792,12 +2792,10 @@
             ;; 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)
@@ -2845,18 +2843,15 @@
   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
-  (if (js2-jump-node-label n)
-      (js2-visit-ast (js2-jump-node-label 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
             (:include js2-jump-node)
             (: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
@@ -2877,9 +2872,7 @@
             (: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:
@@ -2904,18 +2897,18 @@
                                                        (ftype 'FUNCTION)
                                                        (form 
'FUNCTION_STATEMENT)
                                                        (name "")
-                                                       params
+                                                       params rest-p
                                                        body
-                                                       lp
-                                                       rp)))
+                                                       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}
   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
@@ -2928,8 +2921,7 @@
 (put 'cl-struct-js2-function-node 'js2-printer 'js2-print-function-node)
 
 (defun js2-visit-function-node (n v)
-  (if (js2-function-node-name n)
-      (js2-visit-ast (js2-function-node-name n) v))
+  (js2-visit-ast (js2-function-node-name n) v)
   (dolist (p (js2-function-node-params n))
     (js2-visit-ast p v))
   (js2-visit-ast (js2-function-node-body n) v))
@@ -2939,6 +2931,7 @@
         (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
@@ -2951,9 +2944,11 @@
           for param in params
           for count from 1
           do
+          (when (and rest-p (= count len))
+            (insert "..."))
           (js2-print-ast param 0)
-          (if (< count len)
-              (insert ", ")))
+          (when (< count len)
+            (insert ", ")))
     (insert ") {")
     (unless expr
       (insert "\n"))
@@ -2963,7 +2958,7 @@
     (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))))
@@ -2978,14 +2973,13 @@
             (:constructor nil)
             (:constructor make-js2-var-decl-node (&key (type js2-VAR)
                                                        (pos js2-token-beg)
-                                                       len
-                                                       kids
+                                                       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)
@@ -3001,7 +2995,7 @@
     (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"))))
@@ -3019,8 +3013,7 @@
             (: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."
@@ -3032,8 +3025,7 @@
 
 (defun js2-visit-var-init-node (n v)
   (js2-visit-ast (js2-var-init-node-target n) v)
-  (if (js2-var-init-node-initializer n)
-      (js2-visit-ast (js2-var-init-node-initializer n) v)))
+  (js2-visit-ast (js2-var-init-node-initializer n) v))
 
 (defun js2-print-var-init-node (n i)
   (let ((pad (js2-make-pad i))
@@ -3054,8 +3046,7 @@
                                                    test-expr
                                                    true-expr
                                                    false-expr
-                                                   q-pos
-                                                   c-pos)))
+                                                   q-pos c-pos)))
   "AST node for the ternary operator"
   test-expr
   true-expr
@@ -3085,10 +3076,8 @@
             (: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."
@@ -3100,10 +3089,8 @@
 (put 'cl-struct-js2-infix-node 'js2-printer 'js2-print-infix-node)
 
 (defun js2-visit-infix-node (n v)
-  (when (js2-infix-node-left n)
-    (js2-visit-ast (js2-infix-node-left n) v))
-  (when (js2-infix-node-right n)
-    (js2-visit-ast (js2-infix-node-right n) v)))
+  (js2-visit-ast (js2-infix-node-left n) v)
+  (js2-visit-ast (js2-infix-node-right n) v))
 
 (defconst js2-operator-tokens
   (let ((table (make-hash-table :test 'eq))
@@ -3175,10 +3162,8 @@
             (: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.")
 
@@ -3190,8 +3175,7 @@
             (: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
@@ -3225,11 +3209,8 @@
             (:constructor nil)
             (:constructor make-js2-let-node (&key (type js2-LETEXPR)
                                                   (pos js2-token-beg)
-                                                  len
-                                                  vars
-                                                  body
-                                                  lp
-                                                  rp)))
+                                                  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'
@@ -3241,10 +3222,8 @@
 (put 'cl-struct-js2-let-node 'js2-printer 'js2-print-let-node)
 
 (defun js2-visit-let-node (n v)
-  (when (js2-let-node-vars n)
-    (js2-visit-ast (js2-let-node-vars n) v))
-  (when (js2-let-node-body n)
-    (js2-visit-ast (js2-let-node-body n) v)))
+  (js2-visit-ast (js2-let-node-vars n) v)
+  (js2-visit-ast (js2-let-node-body n) v))
 
 (defun js2-print-let-node (n i)
   (insert (js2-make-pad i) "let (")
@@ -3269,15 +3248,15 @@
   (insert (js2-make-pad i)
           (let ((tt (js2-node-type n)))
             (cond
-             ((= tt 'js2-THIS) "this")
-             ((= tt 'js2-NULL) "null")
-             ((= tt 'js2-TRUE) "true")
-             ((= tt 'js2-FALSE) "false")
-             ((= tt 'js2-DEBUGGER) "debugger")
+             ((= tt js2-THIS) "this")
+             ((= tt js2-NULL) "null")
+             ((= tt js2-TRUE) "true")
+             ((= tt js2-FALSE) "false")
+             ((= tt js2-DEBUGGER) "debugger")
              (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
@@ -3285,15 +3264,12 @@
             (:constructor nil)
             (:constructor make-js2-new-node (&key (type js2-NEW)
                                                   (pos js2-token-beg)
-                                                  len
-                                                  target
-                                                  args
-                                                  initializer
-                                                  lp
-                                                  rp)))
+                                                  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'
@@ -3305,8 +3281,7 @@
   (js2-visit-ast (js2-new-node-target n) v)
   (dolist (arg (js2-new-node-args n))
     (js2-visit-ast arg v))
-  (when (js2-new-node-initializer n)
-    (js2-visit-ast (js2-new-node-initializer n) v)))
+  (js2-visit-ast (js2-new-node-initializer n) v))
 
 (defun js2-print-new-node (n i)
   (insert (js2-make-pad i) "new ")
@@ -3362,7 +3337,7 @@
 
 (defun js2-print-number-node (n i)
   (insert (js2-make-pad i)
-          (number-to-string (js2-number-node-value n))))
+          (number-to-string (js2-number-node-num-value n))))
 
 (defstruct (js2-regexp-node
             (:include js2-node)
@@ -3371,8 +3346,7 @@
                                                      (pos js2-token-beg)
                                                      (len (- js2-ts-cursor
                                                              js2-token-beg))
-                                                     value
-                                                     flags)))
+                                                     value flags)))
   "AST node for a regular expression literal."
   value  ; the regexp string, without // delimiters
   flags) ; a string of flags, e.g. `mi'.
@@ -3413,8 +3387,7 @@
             (: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.
 
@@ -3423,8 +3396,7 @@
 
 (defun js2-visit-array-node (n v)
   (dolist (e (js2-array-node-elems n))
-    (when e  ; can be nil, e.g. [a, ,b]
-      (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) "[")
@@ -3438,8 +3410,10 @@
                                                      (pos js2-ts-cursor)
                                                      len
                                                      elems)))
-  "AST node for an object literal expression."
-  elems)  ; a lisp list of `js2-object-prop-node'
+  "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)
 
 (put 'cl-struct-js2-object-node 'js2-visitor 'js2-visit-object-node)
 (put 'cl-struct-js2-object-node 'js2-printer 'js2-print-object-node)
@@ -3458,10 +3432,8 @@
             (: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.")
@@ -3472,7 +3444,7 @@
 (defun js2-print-object-prop-node (n i)
   (insert (js2-make-pad i))
   (js2-print-ast (js2-object-prop-node-left n) 0)
-  (insert ":")
+  (insert ": ")
   (js2-print-ast (js2-object-prop-node-right n) 0))
 
 (defstruct (js2-getter-setter-node
@@ -3480,9 +3452,7 @@
             (: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
@@ -3505,19 +3475,15 @@
             (: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)
 (put 'cl-struct-js2-prop-get-node 'js2-printer 'js2-print-prop-get-node)
 
 (defun js2-visit-prop-get-node (n v)
-  (when (js2-prop-get-node-left n)
-    (js2-visit-ast (js2-prop-get-node-left n) v))
-  (when (js2-prop-get-node-right n)
-    (js2-visit-ast (js2-prop-get-node-right n) v)))
+  (js2-visit-ast (js2-prop-get-node-left n) v)
+  (js2-visit-ast (js2-prop-get-node-right n) v))
 
 (defun js2-print-prop-get-node (n i)
   (insert (js2-make-pad i))
@@ -3530,11 +3496,8 @@
             (: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
@@ -3545,10 +3508,8 @@
 (put 'cl-struct-js2-elem-get-node 'js2-printer 'js2-print-elem-get-node)
 
 (defun js2-visit-elem-get-node (n v)
-  (when (js2-elem-get-node-target n)
-    (js2-visit-ast (js2-elem-get-node-target n) v))
-  (when (js2-elem-get-node-element n)
-    (js2-visit-ast (js2-elem-get-node-element n) v)))
+  (js2-visit-ast (js2-elem-get-node-target n) v)
+  (js2-visit-ast (js2-elem-get-node-element n) v))
 
 (defun js2-print-elem-get-node (n i)
   (insert (js2-make-pad i))
@@ -3562,14 +3523,11 @@
             (: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
 
@@ -3593,8 +3551,7 @@
             (:constructor nil)
             (:constructor make-js2-yield-node (&key (type js2-YIELD)
                                                     (pos js2-ts-cursor)
-                                                    len
-                                                    value)))
+                                                    len value)))
   "AST node for yield statement or expression."
   value) ; optional:  value to be yielded
 
@@ -3616,8 +3573,7 @@
             (: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."
@@ -3640,16 +3596,12 @@
             (:constructor nil)
             (:constructor make-js2-array-comp-node (&key (type js2-ARRAYCOMP)
                                                          (pos js2-ts-cursor)
-                                                         len
-                                                         result
-                                                         loops
-                                                         filter
-                                                         if-pos
-                                                         lp
-                                                         rp)))
+                                                         len result
+                                                         loops filter
+                                                         if-pos lp rp)))
   "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'
+  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
@@ -3662,8 +3614,7 @@
   (js2-visit-ast (js2-array-comp-node-result n) v)
   (dolist (l (js2-array-comp-node-loops n))
     (js2-visit-ast l v))
-  (if (js2-array-comp-node-filter n)
-      (js2-visit-ast (js2-array-comp-node-filter n) v)))
+  (js2-visit-ast (js2-array-comp-node-filter n) v))
 
 (defun js2-print-array-comp-node (n i)
   (let ((pad (js2-make-pad i))
@@ -3677,22 +3628,21 @@
       (js2-print-ast l 0))
     (when filter
       (insert " if (")
-      (js2-print-ast filter 0))
-    (insert ")]")))
+      (js2-print-ast filter 0)
+      (insert ")"))
+    (insert "]")))
 
 (defstruct (js2-array-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
+                                                              len iterator
+                                                              object in-pos
                                                               foreach-p
                                                               each-pos
-                                                              lp
-                                                              rp)))
+                                                              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)
@@ -3703,9 +3653,13 @@
   (js2-visit-ast (js2-array-comp-loop-node-object n) v))
 
 (defun js2-print-array-comp-loop (n i)
-  (insert "for (")
+  (insert "for ")
+  (when (js2-array-comp-loop-node-foreach-p n) (insert "each "))
+  (insert "(")
   (js2-print-ast (js2-array-comp-loop-node-iterator n) 0)
-  (insert " in ")
+  (if (js2-array-comp-loop-node-forof-p n)
+      (insert " of ")
+    (insert " in "))
   (js2-print-ast (js2-array-comp-loop-node-object n) 0)
   (insert ")"))
 
@@ -3725,8 +3679,7 @@
             (:constructor nil)
             (:constructor make-js2-xml-node (&key (type js2-XML)
                                                   (pos js2-token-beg)
-                                                  len
-                                                  kids)))
+                                                  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.")
@@ -3743,8 +3696,7 @@
             (: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
@@ -3766,11 +3718,8 @@
             (: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
@@ -3826,10 +3775,8 @@
             (:constructor nil)
             (:constructor make-js2-xml-prop-ref-node (&key (type js2-REF_NAME)
                                                            (pos js2-token-beg)
-                                                           len
-                                                           propname
-                                                           namespace
-                                                           at-pos
+                                                           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.
@@ -3849,10 +3796,8 @@
 (put 'cl-struct-js2-xml-prop-ref-node 'js2-printer 
'js2-print-xml-prop-ref-node)
 
 (defun js2-visit-xml-prop-ref-node (n v)
-  (if (js2-xml-prop-ref-node-namespace n)
-      (js2-visit-ast (js2-xml-prop-ref-node-namespace n) v))
-  (if (js2-xml-prop-ref-node-propname n)
-      (js2-visit-ast (js2-xml-prop-ref-node-propname n) v)))
+  (js2-visit-ast (js2-xml-prop-ref-node-namespace n) v)
+  (js2-visit-ast (js2-xml-prop-ref-node-propname n) v))
 
 (defun js2-print-xml-prop-ref-node (n i)
   (insert (js2-make-pad i))
@@ -3869,12 +3814,8 @@
             (: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
+                                                           len expr lb rb
+                                                           namespace at-pos
                                                            colon-pos)))
   "AST node for an E4X XML [expr] member-ref expression.
 Syntax:
@@ -3901,10 +3842,8 @@
 (put 'cl-struct-js2-xml-elem-ref-node 'js2-printer 
'js2-print-xml-elem-ref-node)
 
 (defun js2-visit-xml-elem-ref-node (n v)
-  (if (js2-xml-elem-ref-node-namespace n)
-      (js2-visit-ast (js2-xml-elem-ref-node-namespace n) v))
-  (if (js2-xml-elem-ref-node-expr n)
-      (js2-visit-ast (js2-xml-elem-ref-node-expr n) v)))
+  (js2-visit-ast (js2-xml-elem-ref-node-namespace n) v)
+  (js2-visit-ast (js2-xml-elem-ref-node-expr n) v))
 
 (defun js2-print-xml-elem-ref-node (n i)
   (insert (js2-make-pad i))
@@ -3925,15 +3864,12 @@
             (: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)
@@ -3960,8 +3896,7 @@
             (: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'
 
@@ -3982,9 +3917,7 @@
             (: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.
@@ -4011,9 +3944,7 @@
             (: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'
@@ -4039,8 +3970,7 @@
             (: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
 
@@ -4059,11 +3989,8 @@
             (: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'
@@ -4092,10 +4019,9 @@
             (: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)
@@ -4163,7 +4089,7 @@
   (substring (symbol-name (aref n 0))
              (length "cl-struct-")))
 
-(defsubst js2-node-child-list (node)
+(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
@@ -4189,7 +4115,7 @@
    (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)
@@ -4242,7 +4168,7 @@
   (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."
@@ -4277,13 +4203,13 @@
     (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."
   (cond
    ((js2-elem-get-node-p node)
-    (js2-elem-get-node-lb node))
+    (js2-elem-get-node-rb node))
    ((js2-loop-node-p node)
     (js2-loop-node-rp node))
    ((js2-function-node-p node)
@@ -4356,11 +4282,9 @@
         (beg (if (js2-function-node-p parent)
                  (js2-node-abs-pos (js2-function-node-body parent))
                (js2-node-abs-pos parent)))
-        kid
-        result
-        fn
+        kid result fn
         (continue t))
-    (setq fn (if after '> '<))
+    (setq fn (if after '>= '<))
     (while (and kids continue)
       (setq kid (car kids))
       (if (funcall fn (+ beg (js2-node-pos kid)) pos)
@@ -4397,7 +4321,7 @@
          (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."
@@ -4408,13 +4332,10 @@
     (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)
@@ -4512,8 +4433,7 @@
 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
@@ -4568,25 +4488,26 @@
       (setq node (js2-node-parent node)))
     node))
 
-(defsubst js2-nested-function-p (node)
+(defun js2-nested-function-p (node)
   "Return t if NODE is a nested function, or is inside a nested function."
-  (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)))))
+  (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-mode-shift-kids (kids start offset)
+(defun 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)
+(defun 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)))
@@ -4614,7 +4535,7 @@
         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 ", ")))))
 
@@ -4716,11 +4637,14 @@
     (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
-        (js2-node-has-side-effects (js2-expr-stmt-node-expr node)))
+        (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))))))
        ((= tt js2-COMMA)
         (js2-node-has-side-effects (js2-infix-node-right node)))
        ((or (= tt js2-AND)
@@ -4794,7 +4718,7 @@
 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)
@@ -4881,7 +4805,7 @@
                                       js2-END_YIELDS)))))
 
 (defun js2-end-check-if (node)
-  "Return 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))
@@ -4894,7 +4818,7 @@
 
 (defun js2-end-check-switch (node)
   "Consistency of return statements is checked between the case statements.
-If there is no default, then the switch can fall through.  If there is a
+If there is no default, then the switch can fall through. If there is a
 default, we check to see if all code paths in the default return or if
 there is a code path that can fall through.
 Returns logical OR of END_* flags."
@@ -4917,8 +4841,8 @@
 
 (defun js2-end-check-try (node)
  "If the block has a finally, return consistency is checked in the
-finally block.  If all code paths in the finally return, then the
-returns in the try-catch blocks don't matter.  If there is a code path
+finally block. If all code paths in the finally return, then the
+returns in the try-catch blocks don't matter. If there is a code path
 that does not return or if there is no finally block, the returns
 of the try and catch blocks are checked for mismatch.
 Returns logical OR of END_* flags."
@@ -4943,8 +4867,8 @@
   "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.
+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 ... ) {}
@@ -4960,7 +4884,7 @@
                       (js2-do-node-condition node))
                      ((js2-for-node-p node)
                       (js2-for-node-condition node)))))
-                      
+
     ;; check to see if the loop condition is always true
     (if (and condition
              (eq (js2-always-defined-boolean-p condition) 'ALWAYS_TRUE))
@@ -5013,9 +4937,9 @@
 (defun js2-end-check (node)
   "Examine the body of a function, doing a basic reachability analysis.
 Returns a combination of flags END_* flags that indicate
-how the function execution can terminate.  These constitute only the
-pessimistic set of termination conditions.  It is possible that at
-runtime certain code paths will never be actually taken.  Hence this
+how the function execution can terminate. These constitute only the
+pessimistic set of termination conditions. It is possible that at
+runtime certain code paths will never be actually taken. Hence this
 analysis will flag errors in cases where there may not be errors.
 Returns logical OR of END_* flags"
   (let (kid)
@@ -5087,8 +5011,9 @@
           do
           (unless (or (memq sym '(js2-EOF_CHAR js2-ERROR))
                       (not (boundp sym)))
-            (aset names (symbol-value sym)         ; code, e.g. 152
-                  (substring (symbol-name sym) 4)) ; name, e.g. "LET"
+            (aset names (symbol-value sym)           ; code, e.g. 152
+                  (downcase
+                   (substring (symbol-name sym) 4))) ; name, e.g. "let"
             (push sym js2-tokens)))
     names)
   "Vector mapping int values to token string names, sans `js2-' prefix.")
@@ -5114,7 +5039,7 @@
 (defconst js2-token-codes
   (let ((table (make-hash-table :test 'eq :size 256)))
     (loop for name across js2-token-names
-          for sym = (intern (concat "js2-" name))
+          for sym = (intern (concat "js2-" (upcase name)))
           do
           (puthash sym (symbol-value sym) table))
     ;; clean up a few that are "wrong" in Rhino's token codes
@@ -5127,7 +5052,7 @@
   (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)
+(defun js2-report-scan-error (msg &optional no-throw beg len)
   (setq js2-token-end js2-ts-cursor)
   (js2-report-error msg nil
                     (or beg js2-token-beg)
@@ -5135,7 +5060,7 @@
   (unless no-throw
     (throw 'return js2-ERROR)))
 
-(defsubst js2-get-string-from-buffer ()
+(defun 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
@@ -5156,7 +5081,7 @@
 
 ;; 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.
@@ -5177,7 +5102,7 @@
       ;; 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.
@@ -5193,7 +5118,7 @@
       (if (string-match "[a-zA-Z0-9]\\{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)))
@@ -5202,18 +5127,18 @@
       (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-java-identifier-start-p (c)
   (or
    (memq c '(?$ ?_))
    (js2-char-uppercase-p c)
    (js2-char-lowercase-p c)))
 
-(defsubst js2-java-identifier-part-p (c)
+(defun js2-java-identifier-part-p (c)
   "Implementation of java.lang.Character.isJavaIdentifierPart()."
   ;; TODO:  make me Unicode-friendly.  See comments above.
   (or
@@ -5222,7 +5147,7 @@
    (js2-char-lowercase-p c)
    (and (>= c ?0) (<= c ?9))))
 
-(defsubst js2-alpha-p (c)
+(defun js2-alpha-p (c)
   (cond ((and (<= ?A c) (<= c ?Z)) t)
         ((and (<= ?a c) (<= c ?z)) t)
         (t nil)))
@@ -5230,7 +5155,7 @@
 (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
@@ -5240,7 +5165,7 @@
 
 (defconst js2-eol-chars (list js2-EOF_CHAR ?\n ?\r))
 
-(defsubst js2-skip-line ()
+(defun js2-skip-line ()
   "Skip to end of line."
   (let (c)
     (while (not (memq (setq c (js2-get-char)) js2-eol-chars)))
@@ -5249,7 +5174,7 @@
 
 (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
@@ -5370,7 +5295,7 @@
     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
@@ -5400,7 +5325,7 @@
   (setq js2-token-end js2-ts-cursor)
   (throw 'return token))
 
-(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."
@@ -5425,21 +5350,9 @@
 
 (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)
+  (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)
     (catch 'return
       (while t
         ;; Eat whitespace, possibly sensitive to newlines.
@@ -5525,7 +5438,7 @@
                        (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))
+              (if (not (eq result 'js2-RESERVED))
                   (throw 'return (js2-token-code result)))
               (js2-report-warning "msg.reserved.keyword" str)))
           ;; If we want to intern these as Rhino does, just use (intern str)
@@ -5714,7 +5627,9 @@
              (throw 'return js2-COLON)))
           (?.
            (if (js2-match-char ?.)
-               (js2-ts-return js2-DOTDOT)
+               (if (js2-match-char ?.)
+                   (js2-ts-return js2-TRIPLEDOT)
+                 (js2-ts-return js2-DOTDOT))
              (if (js2-match-char ?\()
                  (js2-ts-return js2-DOTQUERY)
                (throw 'return js2-DOT))))
@@ -5856,8 +5771,7 @@
 
 (defun js2-read-regexp (start-token)
   "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))
@@ -5906,7 +5820,7 @@
             js2-ts-regexp-flags (js2-collect-string flags)
             js2-token-end js2-ts-cursor)
       ;; tell `parse-partial-sexp' to ignore this range of chars
-      (put-text-property js2-token-beg js2-token-end 'syntax-class '(2)))))
+      (js2-record-text-property js2-token-beg js2-token-end 'syntax-class 
'(2)))))
 
 (defun js2-get-first-xml-token ()
   (setq js2-ts-xml-open-tags-count 0
@@ -5915,7 +5829,7 @@
   (js2-unget-char)
   (js2-get-next-xml-token))
 
-(defsubst js2-xml-discard-string ()
+(defun js2-xml-discard-string ()
   "Throw away the string in progress and flag an XML parse error."
   (setq js2-ts-string-buffer nil
         js2-ts-string nil)
@@ -6009,7 +5923,11 @@
                            (throw 'return js2-ERROR)))
                         (t
                          (unless (js2-read-entity)
-                           (throw 'return js2-ERROR)))))
+                           (throw 'return js2-ERROR))))
+                      ;; 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)
@@ -6036,7 +5954,7 @@
                    (js2-add-to-string c))))))))
     (setq js2-token-end js2-ts-cursor)
     result))
-        
+
 (defun js2-read-quoted-string (quote)
   (let (c)
     (catch 'return
@@ -6120,7 +6038,7 @@
 
 ;;; 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)
@@ -6129,9 +6047,9 @@
           end (max (point-min) end))
     (if record
         (push (list beg end face) js2-mode-fontifications)
-      (put-text-property beg end 'face face))))
+      (put-text-property beg end 'font-lock-face face))))
 
-(defsubst js2-set-kid-face (pos kid len face)
+(defun 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.
@@ -6145,15 +6063,11 @@
   (js2-set-face start (+ start length) 'font-lock-keyword-face))
 
 (defsubst js2-clear-face (beg end)
-  (remove-text-properties beg end '(face nil
+  (remove-text-properties beg end '(font-lock-face nil
                                     help-echo nil
                                     point-entered nil
                                     c-in-sws nil)))
 
-(defsubst js2-record-text-property (beg end prop value)
-  "Record a text property to set when parsing finishes."
-  (push (list beg end prop value) js2-mode-deferred-properties))
-
 (defconst js2-ecma-global-props
   (concat "^"
           (regexp-opt
@@ -6244,6 +6158,8 @@
       "toTimeString" "toUTCString"
       ;; properties of the RegExp prototype object
       "exec" "test"
+      ;; properties of the JSON prototype object
+      "parse" "stringify"
       ;; SpiderMonkey/Rhino extensions, versions 1.5+
       "toSource" "__defineGetter__" "__defineSetter__"
       "__lookupGetter__" "__lookupSetter__" "__noSuchMethod__"
@@ -6253,7 +6169,7 @@
   "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)))
@@ -6261,8 +6177,7 @@
         (level1 (>= js2-highlight-level 1))
         (level2 (>= js2-highlight-level 2))
         (level3 (>= js2-highlight-level 3))
-        pos
-        face)
+        pos face)
     (when level2
       (if call-p
           (cond
@@ -6297,7 +6212,7 @@
         (js2-set-face (setq pos (+ (js2-node-pos parent) ; absolute
                                    (js2-node-pos prop))) ; relative
                       (+ pos (js2-node-len prop))
-                      face)))))
+                      face 'record)))))
 
 (defun js2-parse-highlight-member-expr-node (node)
   "Perform syntax highlighting of EcmaScript built-in properties.
@@ -6316,7 +6231,7 @@
         (when face
           (setq pos (js2-node-pos node)
                 end (+ pos (js2-node-len node)))
-          (js2-set-face pos end face))))
+          (js2-set-face pos end face 'record))))
      ;; case 2:  property access or function call
      ((or (js2-prop-get-node-p node)
           ;; highlight function call if expr is a prop-get node
@@ -6366,7 +6281,7 @@
           "\\(?:param\\|argument\\)"
           "\\)"
           "\\s-*\\({[^}]+}\\)?"         ; optional type
-          "\\s-*\\([a-zA-Z0-9_$]+\\)?"  ; name
+          "\\s-*\\[?\\([a-zA-Z0-9_$\.]+\\)?\\]?"  ; name
           "\\>")
   "Matches jsdoc tags with optional type and optional param name.")
 
@@ -6457,7 +6372,7 @@
              ))
           "\\)\\)\\s-*")
   "Matches empty jsdoc tags.")
-  
+
 (defconst js2-jsdoc-link-tag-regexp
   "{\\(@\\(?:link\\|code\\)\\)\\s-+\\([^#}\n]+\\)\\(#.+\\)?}"
   "Matches a jsdoc link or code tag.")
@@ -6470,7 +6385,7 @@
   "\\(</?\\)\\([a-zA-Z]+\\)\\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)
@@ -6539,35 +6454,35 @@
         (js2-set-face (setq leftpos (js2-node-abs-pos name))
                       (+ leftpos (js2-node-len name))
                       'font-lock-function-name-face
-                      'record)))
-    ;; save variable assignments so we can check for undeclared later
-    ;; (can't do it here since var decls can come at end of script)
-    (when (and js2-highlight-external-variables
-               (setq name (js2-member-expr-leftmost-name left)))
-      (push (list name js2-current-scope
-                  (setq leftpos (js2-node-abs-pos name))
-                  (setq end (+ leftpos (js2-node-len name))))
-            js2-recorded-assignments))))
+                      'record)))))
+
+(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)
+    (push (list node js2-current-scope
+                (setq leftpos (js2-node-abs-pos node))
+                (setq end (+ leftpos (js2-node-len node))))
+          js2-recorded-identifiers)))
 
 (defun js2-highlight-undeclared-vars ()
-  "After entire parse is finished, look for undeclared variable assignments.
+  "After entire parse is finished, look for undeclared variable references.
 We have to wait until entire buffer is parsed, since JavaScript permits var
 decls to occur after they're used.
 
 If any undeclared var name is in `js2-externs' or `js2-additional-externs',
 it is considered declared."
   (let (name)
-    (dolist (entry js2-recorded-assignments)
+    (dolist (entry js2-recorded-identifiers)
       (destructuring-bind (name-node scope pos end) entry
         (setq name (js2-name-node-name name-node))
         (unless (or (member name js2-global-externs)
                     (member name js2-default-externs)
                     (member name js2-additional-externs)
                     (js2-get-defining-scope scope name))
-          (js2-set-face pos end 'js2-external-variable 'record)
-          (js2-record-text-property pos end 'help-echo "Undeclared variable")
-          (js2-record-text-property pos end 'point-entered #'js2-echo-help))))
-    (setq js2-recorded-assignments nil)))
+          (js2-report-warning "msg.undeclared.variable" name pos (- end pos)
+                              'js2-external-variable))))
+    (setq js2-recorded-identifiers nil)))
 
 ;;; IMenu support
 
@@ -6575,7 +6490,7 @@
 ;; possibly other browsing mechanisms.
 
 ;; The basic strategy is to identify function assignment targets of the form
-;; `foo.bar.baz', convert them to (list foo bar baz <position>), and push the
+;; `foo.bar.baz', convert them to (list fn foo bar baz <position>), and push 
the
 ;; list into `js2-imenu-recorder'.  The lists are merged into a trie-like tree
 ;; for imenu after parsing is finished.
 
@@ -6624,13 +6539,14 @@
 ;; During parsing we accumulate an entry for each definition in
 ;; the variable `js2-imenu-recorder', like so:
 
-;; '((a 5)
-;;   (b 25)
-;;   (foo 100)
-;;   (foo bar 200)
-;;   (foo bar baz 300)
-;;   (foo bar zab 400))
+;; '((fn a 5)
+;;   (fn b 25)
+;;   (fn foo 100)
+;;   (fn foo bar 200)
+;;   (fn foo bar baz 300)
+;;   (fn foo bar zab 400))
 
+;; Where 'fn' is the respective function node.
 ;; After parsing these entries are merged into this alist-trie:
 
 ;; '((a . 1)
@@ -6644,7 +6560,7 @@
 ;; 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."
@@ -6659,8 +6575,8 @@
    ((js2-this-node-p node)
     "this")))
 
-(defsubst js2-node-qname-component (node)
-  "Test function:  return the name of this node, if it contributes to a qname.
+(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
    (or (js2-prop-node-name node)
@@ -6668,12 +6584,14 @@
                 (js2-function-node-name node))
            (js2-name-node-name (js2-function-node-name node))))))
 
-(defsubst js2-record-function-qname (fn-node qname)
-  "Associate FN-NODE with its QNAME for later lookup.
-This is used in postprocessing the chain list.  When we find a chain
-whose first element is a js2-THIS keyword node, we look up the parent
-function and see (using this map) whether it is the tail of a chain.
-If so, we replace the this-node with a copy of the parent's qname."
+(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.
+
+Associate FN-NODE with its QNAME for later lookup.
+This is used in postprocessing the chain list.  For each chain, we find
+the parent function, look up its qname, then prepend a copy of it to the 
chain."
+  (push (cons fn-node (append qname (list pos))) js2-imenu-recorder)
   (unless js2-imenu-function-map
     (setq js2-imenu-function-map (make-hash-table :test 'eq)))
   (puthash fn-node qname js2-imenu-function-map))
@@ -6681,7 +6599,8 @@
 (defun js2-record-imenu-functions (node &optional var)
   "Record function definitions for imenu.
 NODE is a function node or an object literal.
-VAR, if non-nil, is the expression that NODE is being assigned to."
+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)
@@ -6690,77 +6609,74 @@
        ((and fun-p
              (not var)
              (setq fname-node (js2-function-node-name node)))
-        (push (setq qname (list fname-node (js2-node-pos node)))
-              js2-imenu-recorder)
-        (js2-record-function-qname node qname))
+        (js2-record-imenu-entry node (list fname-node) (js2-node-pos node)))
        ;; for remaining forms, compute left-side tree branch first
        ((and var (setq qname (js2-compute-nested-prop-get var)))
         (cond
          ;; foo.bar.baz = function
          (fun-p
-          (push (nconc qname (list (js2-node-pos node)))
-                js2-imenu-recorder)
-          (js2-record-function-qname node qname))
+          (js2-record-imenu-entry node qname (js2-node-pos node)))
          ;; foo.bar.baz = object-literal
          ;; look for nested functions:  {a: {b: function() {...} }}
          ((js2-object-node-p node)
-          (js2-record-object-literal node qname))))))))
+          ;; Node position here is still absolute, since the parser
+          ;; passes the assignment target and value expressions
+          ;; to us before they are added as children of the assignment node.
+          (js2-record-object-literal node qname (js2-node-pos node)))))))))
 
 (defun js2-compute-nested-prop-get (node)
-  "If NODE is of form foo.bar.baz, return component nodes as a list.
-Otherwise returns nil.  Element-gets can be treated as property-gets
-if the index expression is a name, a string, or a positive integer."
+  "If NODE is of form foo.bar, foo['bar'], or any nested combination, return
+component nodes as a list.  Otherwise return nil.  Element-gets are treated
+as property-gets if the index expression is a string, or a positive integer."
   (let (left right head)
     (cond
      ((or (js2-name-node-p node)
           (js2-this-node-p node))
       (list node))
      ;; foo.bar.baz is parenthesized as (foo.bar).baz => right operand is a 
leaf
-     ((js2-prop-get-node-p node)  ; includes elem-get nodes
+     ((js2-prop-get-node-p node)        ; foo.bar
       (setq left (js2-prop-get-node-left node)
             right (js2-prop-get-node-right node))
-      (if (and (or (js2-prop-get-node-p left)     ; left == foo.bar
-                   (js2-name-node-p left)
-                   (js2-this-node-p left))        ; or left == foo
-               (or (js2-name-node-p right)        ; .bar
-                   (js2-string-node-p right)      ; ['bar']
-                   (and (js2-number-node-p right) ; [10]
-                        (string-match "^[0-9]+$"
-                                      (js2-number-node-value right)))))
+      (if (setq head (js2-compute-nested-prop-get left))
+          (nconc head (list right))))
+     ((js2-elem-get-node-p node)        ; foo['bar'] or foo[101]
+      (setq left (js2-elem-get-node-target node)
+            right (js2-elem-get-node-element node))
+      (if (or (js2-string-node-p right)      ; ['bar']
+              (and (js2-number-node-p right) ; [10]
+                   (string-match "^[0-9]+$"
+                                 (js2-number-node-value right))))
           (if (setq head (js2-compute-nested-prop-get left))
               (nconc head (list right))))))))
 
-(defun js2-record-object-literal (node qname)
+(defun js2-record-object-literal (node qname pos)
   "Recursively process an object literal looking for functions.
 NODE is an object literal that is the right-hand child of an assignment
 expression.  QNAME is a list of nodes representing the assignment target,
 e.g. for foo.bar.baz = {...}, QNAME is (foo-node bar-node baz-node).
-We do a depth-first traversal of NODE.  Any functions we find are prefixed
-with QNAME plus the property name of the function and appended to the
-variable `js2-imenu-recorder'."
-  ;; Elements are relative to parent position, which is still absolute,
-  ;; since the parser passes the assignment target and value expressions
-  ;; to us before they are added as children of the assignment node.
-  (let ((pos (js2-node-pos node))
-        left right)
+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)
     (dolist (e (js2-object-node-elems node))  ; e is a `js2-object-prop-node'
-      (setq left (js2-infix-node-left e))
-      (cond
-       ;; foo: function() {...}
-       ((js2-function-node-p (setq right (js2-infix-node-right e)))
-        (when (js2-prop-node-name left)
-          ;; As a policy decision, we record the position of the property,
-          ;; not the position of the `function' keyword, since the property
-          ;; is effectively the name of the function.
-          (push (append qname (list left) (list (+ pos (js2-node-pos e))))
-                js2-imenu-recorder)
-          (js2-record-function-qname right qname)))
-       ;; foo: {object-literal} -- add foo to qname and recurse
-       ((js2-object-node-p right)
-        (js2-record-object-literal right
-                                   (append qname (list (js2-infix-node-left 
e)))))))))
+      (let ((left (js2-infix-node-left e))
+            ;; Element positions are relative to the parent position.
+            (pos (+ pos (js2-node-pos e))))
+        (cond
+         ;; foo: function() {...}
+         ((js2-function-node-p (setq right (js2-infix-node-right e)))
+          (when (js2-prop-node-name left)
+            ;; As a policy decision, we record the position of the property,
+            ;; not the position of the `function' keyword, since the property
+            ;; is effectively the name of the function.
+            (js2-record-imenu-entry right (append qname (list left)) pos)))
+         ;; foo: {object-literal} -- add foo to qname, offset position, and 
recurse
+         ((js2-object-node-p right)
+          (js2-record-object-literal right
+                                     (append qname (list (js2-infix-node-left 
e)))
+                                     (+ pos (js2-node-pos right)))))))))
 
-(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."
@@ -6776,31 +6692,54 @@
       (js2-ast-root-p defining-scope))
      (t t))))
 
-(defun js2-browse-postprocess-chains (chains)
+(defun js2-wrapper-function-p (node)
+  "Returns t if NODE is a function expression that's immediately invoked.
+NODE must be `js2-function-node'."
+  (let ((parent (js2-node-parent node)))
+    (or
+     ;; function(){...}();
+     (js2-call-node-p parent)
+     (and (js2-paren-node-p parent)
+          ;; (function(){...})();
+          (or (js2-call-node-p (setq parent (js2-node-parent parent)))
+              ;; (function(){...}).call(this);
+              (and (js2-prop-get-node-p parent)
+                   (member (js2-name-node-name (js2-prop-get-node-right 
parent))
+                           '("call" "apply"))
+                   (js2-call-node-p (js2-node-parent parent))))))))
+
+(defun js2-browse-postprocess-chains (entries)
   "Modify function-declaration name chains after parsing finishes.
 Some of the information is only available after the parse tree is complete.
-For instance, following a 'this' reference requires a parent function node."
-  (let (result head fn parent-chain p elem)
-    (dolist (chain chains)
-      ;; examine the head of each node to get its defining scope
-      (setq head (car chain))
-      (cond
-       ;; if top-level/external, keep as-is
-       ((js2-node-top-level-decl-p head)
-        (push chain result))
-       ;; check for a this-reference
-       ((eq (js2-node-type head) js2-THIS)
-        (setq fn (js2-node-parent-script-or-fn head))
-        ;; if there is no parent function, or if the parent function
-        ;; is nested, discard the head node and keep the rest of the chain.
-        (if (or (null fn) (js2-nested-function-p fn))
-            (push (cdr chain) result)
-          ;; else look up parent in function-map.  If not found, discard chain.
-          (when (setq parent-chain (and js2-imenu-function-map
-                                        (gethash fn js2-imenu-function-map)))
-            ;; else discard head node and prefix parent fn qname, which is
-            ;; the parent-chain sans tail, to this chain.
-            (push (append (butlast parent-chain) (cdr chain)) result))))))
+For instance, processing a nested scope requires a parent function node."
+  (let (result head fn current-fn parent-qname qname p elem)
+    (dolist (entry entries)
+      ;; function node goes first
+      (destructuring-bind (current-fn &rest (&whole chain head &rest)) entry
+        ;; Examine head's defining scope:
+        ;; Pre-processed chain, or top-level/external, keep as-is.
+        (if (or (stringp head) (js2-node-top-level-decl-p head))
+            (push chain result)
+          (when (js2-this-node-p head)
+            (setq chain (cdr chain))) ; discard this-node
+          (when (setq fn (js2-node-parent-script-or-fn current-fn))
+            (setq parent-qname (gethash fn js2-imenu-function-map 'not-found))
+            (when (eq parent-qname 'not-found)
+              ;; anonymous function expressions are not recorded
+              ;; during the parse, so we need to handle this case here
+              (setq parent-qname
+                    (if (js2-wrapper-function-p fn)
+                        (let ((grandparent (js2-node-parent-script-or-fn fn)))
+                          (if (js2-ast-root-p grandparent)
+                              nil
+                            (gethash grandparent js2-imenu-function-map 
'skip)))
+                      'skip))
+              (puthash fn parent-qname js2-imenu-function-map))
+            (unless (eq parent-qname 'skip)
+              ;; prefix parent fn qname to this chain.
+              (let ((qname (append parent-qname chain)))
+                (puthash current-fn (butlast qname) js2-imenu-function-map)
+                (push qname result)))))))
     ;; finally replace each node in each chain with its name.
     (dolist (chain result)
       (setq p chain)
@@ -6836,7 +6775,7 @@
 ;; a nested sub-alist element looks like (INDEX-NAME SUB-ALIST).
 ;; The sub-alist entries immediately follow INDEX-NAME, the head of the list.
 
-(defsubst js2-treeify (lst)
+(defun js2-treeify (lst)
   "Convert (a b c d) to (a ((b ((c d)))))."
   (if (null (cddr lst))  ; list length <= 2
       lst
@@ -6844,7 +6783,7 @@
 
 (defun js2-build-alist-trie (chains trie)
   "Merge declaration name chains into a trie-like alist structure for imenu.
-CHAINS is the qname chain list produced during parsing.  TRIE is a
+CHAINS is the qname chain list produced during parsing. TRIE is a
 list of elements built up so far."
   (let (head tail pos branch kids)
     (dolist (chain chains)
@@ -6878,7 +6817,6 @@
                                (1+ (loop for kid in kids
                                          count (eq ?< (aref (car kid) 0)))))
                        pos))))
-
        ;; case 4:  key is there with kids, need to merge in our chain
        (t
         (js2-build-alist-trie (list tail) kids))))
@@ -6938,8 +6876,8 @@
 
 ;;; Parser
 
-(defconst js2-version "1.8.0"
-  "Version of JavaScript supported, plus minor js2 version.")
+(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."
@@ -6952,7 +6890,7 @@
   (+ (js2-node-pos n)
      (js2-node-len n)))
 
-(defsubst js2-record-comment ()
+(defun js2-record-comment ()
   "Record a comment in `js2-scanned-comments'."
   (push (make-js2-comment-node :len (- js2-token-end js2-token-beg)
                                :format js2-ts-comment-type)
@@ -6963,11 +6901,11 @@
                        'font-lock-comment-face))
     (when (memq js2-ts-comment-type '(html preprocessor))
       ;; Tell cc-engine the bounds of the comment.
-      (put-text-property js2-token-beg (1- js2-token-end) 'c-in-sws t))))
+      (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 ()
+(defun js2-peek-token ()
   "Return the next token without consuming it.
 If previous token was consumed, calls scanner to get new token.
 If previous token was -not- consumed, returns it (idempotent).
@@ -7013,25 +6951,25 @@
           (js2-record-face 'font-lock-constant-face))))
       tt)))  ; return unflagged token
 
-(defsubst js2-peek-flagged-token ()
-  "Return the current token along with any flags set for it."
+(defun 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 ()
+(defun js2-next-token ()
   (prog1
       (js2-peek-token)
     (js2-consume-token)))
 
-(defsubst js2-next-flagged-token ()
+(defun js2-next-flagged-token ()
   (js2-peek-token)
   (prog1 js2-current-flagged-token
     (js2-consume-token)))
 
-(defsubst js2-match-token (match)
+(defun js2-match-token (match)
   "Consume and return t if next token matches MATCH, a bytecode.
 Returns nil and consumes nothing if MATCH is not the next token."
   (if (/= (js2-peek-token) match)
@@ -7039,13 +6977,25 @@
     (js2-consume-token)
     t))
 
-(defsubst js2-valid-prop-name-token (tt)
+(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)
+    (js2-record-face 'font-lock-keyword-face)
+    t))
+
+(defun js2-valid-prop-name-token (tt)
   (or (= tt js2-NAME)
-      (and js2-allow-keywords-as-property-names
-           (plusp tt)
-           (aref js2-kwd-tokens tt))))
+      (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)))
 
-(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."
@@ -7055,13 +7005,13 @@
         t)
     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 ()
+(defun js2-peek-token-or-eol ()
   "Return js2-EOL if the current token immediately follows a newline.
 Else returns the current token.  Used in situations where we don't
 consider certain token types valid if they are preceded by a newline.
@@ -7073,11 +7023,11 @@
         js2-EOL
       tt)))
 
-(defsubst js2-set-check-for-label ()
+(defun js2-set-check-for-label ()
   (assert (= (logand js2-current-flagged-token js2-clear-ti-mask) js2-NAME))
   (js2-set-flag js2-current-flagged-token js2-ti-check-label))
 
-(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."
@@ -7089,11 +7039,11 @@
 (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)
@@ -7101,15 +7051,15 @@
                  (gethash name js2-compiler-activation-names)))
         (js2-set-requires-activation))))
 
-(defsubst js2-set-is-generator ()
+(defun js2-set-is-generator ()
   (if (js2-function-node-p js2-current-script-or-fn)
       (setf (js2-function-node-is-generator js2-current-script-or-fn) t)))
 
-(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)))
@@ -7121,7 +7071,7 @@
   (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)
@@ -7134,7 +7084,7 @@
           (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))
@@ -7180,9 +7130,8 @@
         (max-specpdl-size (max max-specpdl-size 3000))
         (case-fold-search nil)
         ast)
-    (or buf (setq buf (current-buffer)))
     (message nil)  ; clear any error message from previous parse
-    (with-current-buffer buf
+    (with-current-buffer (or buf (current-buffer))
       (setq js2-scanned-comments nil
             js2-parsed-errors nil
             js2-parsed-warnings nil
@@ -7217,15 +7166,14 @@
           js2-current-flagged-token js2-EOF
           js2-nesting-of-function 0
           js2-labeled-stmt nil
-          js2-recorded-assignments nil)  ; for js2-highlight
+          js2-recorded-identifiers nil)  ; for js2-highlight
     (while (/= (setq tt (js2-peek-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)))
-            (js2-record-imenu-functions n))
+                                          'FUNCTION_STATEMENT))))
         ;; not a function - parse a statement
         (setq n (js2-parse-statement)))
       ;; add function or statement to script
@@ -7240,9 +7188,11 @@
         (js2-node-add-children root comment)))
     (setf (js2-node-len root) (- end pos))
     ;; Give extensions a chance to muck with things before highlighting starts.
-    (dolist (callback js2-post-parse-callbacks)
-      (funcall callback))
-    (js2-highlight-undeclared-vars)
+    (let ((js2-additional-externs js2-additional-externs))
+      (save-excursion
+        (dolist (callback js2-post-parse-callbacks)
+          (funcall callback)))
+      (js2-highlight-undeclared-vars))
     root))
 
 (defun js2-function-parser ()
@@ -7258,7 +7208,7 @@
                           (- js2-ts-cursor (js2-node-pos fn-node)))
       (js2-node-add-children fn-node
                              (setf (js2-function-node-body fn-node)
-                                   (js2-parse-expr))))))
+                                   (js2-parse-expr t))))))
 
 (defun js2-parse-function-body (fn-node)
   (js2-must-match js2-LC "msg.no.brace.body"
@@ -7287,32 +7237,95 @@
     (js2-node-add-children fn-node pn)
     pn))
 
+(defun js2-define-destruct-symbols (node decl-type face &optional 
ignore-not-in-block)
+  "Declare and fontify destructuring parameters inside NODE.
+NODE is either `js2-array-node', `js2-object-node', or `js2-name-node'."
+  (cond
+   ((js2-name-node-p node)
+    (let (leftpos)
+      (js2-define-symbol decl-type (js2-name-node-name node)
+                         node ignore-not-in-block)
+      (when face
+        (js2-set-face (setq leftpos (js2-node-abs-pos node))
+                      (+ leftpos (js2-node-len node))
+                      face 'record))))
+   ((js2-object-node-p node)
+    (dolist (elem (js2-object-node-elems node))
+      (js2-define-destruct-symbols
+       (if (js2-object-prop-node-p elem)
+           (js2-object-prop-node-right elem)
+         ;; abbreviated destructuring {a, b}
+         elem)
+       decl-type face ignore-not-in-block)))
+   ((js2-array-node-p node)
+    (dolist (elem (js2-array-node-elems node))
+      (when elem
+        (js2-define-destruct-symbols elem decl-type face 
ignore-not-in-block))))
+   (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)
   (if (js2-match-token js2-RP)
       (setf (js2-function-node-rp fn-node) (- js2-token-beg pos))
-    (let (params len param)
+    (let (params len param default-found rest-param-at)
       (loop for tt = (js2-peek-token)
             do
             (cond
              ;; destructuring param
              ((or (= tt js2-LB) (= tt js2-LC))
-              (push (js2-parse-primary-expr) params))
-             ;; simple name
+              (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)
+              (push param params))
+             ;; variable name
              (t
+              (when (and (>= js2-language-version 200)
+                         (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)
               (setq param (js2-create-name-node))
               (js2-define-symbol js2-LP js2-ts-string param)
+              ;; default parameter value
+              (when (or (and default-found
+                             (not rest-param-at)
+                             (js2-must-match js2-ASSIGN
+                                             
"msg.no.default.after.default.param"
+                                             (js2-node-pos param)
+                                             (js2-node-len param)))
+                        (and (>= js2-language-version 200)
+                             (js2-match-token js2-ASSIGN)))
+                (let* ((pos (js2-node-pos param))
+                       (tt js2-current-token)
+                       (op-pos (- js2-token-beg pos))
+                       (left param)
+                       (right (js2-parse-assign-expr))
+                       (len (- (js2-node-end right) pos)))
+                  (setq param (make-js2-assign-node
+                               :type tt :pos pos :len len :op-pos op-pos
+                               :left left :right right)
+                        default-found t)
+                  (js2-node-add-children param left right)))
               (push param params)))
+            (when (and rest-param-at (> (length params) (1+ rest-param-at)))
+              (js2-report-error "msg.param.after.rest" nil
+                                (js2-node-pos param) (js2-node-len param)))
             while
             (js2-match-token js2-COMMA))
-      (if (js2-must-match js2-RP "msg.no.paren.after.parms")
-          (setf (js2-function-node-rp fn-node) (- js2-token-beg pos)))
+      (when (js2-must-match js2-RP "msg.no.paren.after.parms")
+        (setf (js2-function-node-rp fn-node) (- js2-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
@@ -7333,11 +7346,7 @@
 (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
+        name name-beg name-end fn-node lp
         (synthetic-type function-type)
         member-expr-node)
     ;; parse function name, expression, or non-name (anonymous)
@@ -7415,7 +7424,7 @@
                              (js2-name-node-name name)
                              fn-node))
       (if (and name
-               (eq function-type 'FUNCTION_EXPRESSION_STATEMENT))
+               (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
@@ -7494,7 +7503,7 @@
     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
@@ -7585,12 +7594,7 @@
 (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)
+        cond if-true if-false else-pos end pn)
     (js2-consume-token)
     (setq cond (js2-parse-condition)
           if-true (js2-parse-statement)
@@ -7613,17 +7617,8 @@
 (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)
+        tt pn discriminant has-default case-expr case-node
+        case-pos cases stmt lp rp)
     (js2-consume-token)
     (if (js2-must-match js2-LP "msg.no.paren.switch")
         (setq lp js2-token-beg))
@@ -7681,8 +7676,7 @@
   "Parser for while-statement.  Last matched token must be js2-WHILE."
   (let ((pos js2-token-beg)
         (pn (make-js2-while-node))
-        cond
-        body)
+        cond body)
     (js2-consume-token)
     (js2-enter-loop pn)
     (unwind-protect
@@ -7702,9 +7696,7 @@
   "Parser for do-statement.  Last matched token must be js2-DO."
   (let ((pos js2-token-beg)
         (pn (make-js2-do-node))
-        cond
-        body
-        end)
+        cond body end)
     (js2-consume-token)
     (js2-enter-loop pn)
     (unwind-protect
@@ -7732,19 +7724,12 @@
   "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)
-        pn
-        is-for-each
-        is-for-in
-        in-pos
-        each-pos
-        tmp-pos
+        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)
+        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)
@@ -7757,66 +7742,77 @@
     (if (js2-must-match js2-LP "msg.no.paren.for")
         (setq lp (- js2-token-beg for-pos)))
     (setq tt (js2-peek-token))
-    ;; parse init clause
-    (let ((js2-in-for-init t))  ; set as dynamic variable
-      (cond
-       ((= tt js2-SEMI)
-        (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)))
-       (t
-        (setq init (js2-parse-expr)))))
-    (if (js2-match-token js2-IN)
-        (setq is-for-in t
-              in-pos (- js2-token-beg for-pos)
-              cond (js2-parse-expr))  ; object over which we're iterating
-      ;; else ordinary for loop - parse cond and incr
-      (js2-must-match js2-SEMI "msg.no.semi.for")
-      (setq cond (if (= (js2-peek-token) js2-SEMI)
-                     (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
-            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)))
-    (if (not is-for-in)
-        (setq pn (make-js2-for-node :init init
-                                    :condition cond
-                                    :update incr
-                                    :lp lp
-                                    :rp rp))
-      ;; cond could be null if 'in obj' got eaten by the init node.
-      (if (js2-infix-node-p init)
-          ;; it was (foo in bar) instead of (var foo in bar)
-          (setq cond (js2-infix-node-right init)
-                init (js2-infix-node-left init))
-        (if (and (js2-var-decl-node-p init)
-                 (> (length (js2-var-decl-node-kids init)) 1))
-            (js2-report-error "msg.mult.index")))
-      (setq pn (make-js2-for-in-node :iterator init
-                                     :object cond
-                                     :in-pos in-pos
-                                     :foreach-p is-for-each
-                                     :each-pos each-pos
-                                     :lp lp
-                                     :rp rp)))
+    ;; 'for' makes local scope
+    (js2-push-scope (make-js2-scope))
     (unwind-protect
-        (progn
-          (js2-enter-loop pn)
-          ;; We have to parse the body -after- creating the loop node,
-          ;; so that the loop node appears in the js2-loop-set, allowing
-          ;; break/continue statements to find the enclosing loop.
-          (setf body (js2-parse-statement)
-                (js2-loop-node-body pn) body
-                (js2-node-pos pn) for-pos
-                (js2-node-len pn) (- (js2-node-end body) for-pos))
-          (js2-node-add-children pn init cond incr body))
-      ;; finally
-      (js2-exit-loop))
+        ;; parse init clause
+        (let ((js2-in-for-init t))  ; set as dynamic variable
+          (cond
+           ((= tt js2-SEMI)
+            (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)))
+           (t
+            (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)
+                ;; 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)))
+                       (js2-parse-expr)))  ; object over which we're iterating
+        ;; else ordinary for loop - parse cond and incr
+        (js2-must-match js2-SEMI "msg.no.semi.for")
+        (setq cond (if (= (js2-peek-token) js2-SEMI)
+                       (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
+              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)))
+      (if (not is-for-in-or-of)
+          (setq pn (make-js2-for-node :init init
+                                      :condition cond
+                                      :update incr
+                                      :lp lp
+                                      :rp rp))
+        ;; cond could be null if 'in obj' got eaten by the init node.
+        (if (js2-infix-node-p init)
+            ;; it was (foo in bar) instead of (var foo in bar)
+            (setq cond (js2-infix-node-right init)
+                  init (js2-infix-node-left init))
+          (if (and (js2-var-decl-node-p init)
+                   (> (length (js2-var-decl-node-kids init)) 1))
+              (js2-report-error "msg.mult.index")))
+        (setq pn (make-js2-for-in-node :iterator init
+                                       :object cond
+                                       :in-pos in-pos
+                                       :foreach-p is-for-each
+                                       :each-pos each-pos
+                                       :forof-p is-for-of
+                                       :lp lp
+                                       :rp rp)))
+      (unwind-protect
+          (progn
+            (js2-enter-loop pn)
+            ;; We have to parse the body -after- creating the loop node,
+            ;; so that the loop node appears in the js2-loop-set, allowing
+            ;; break/continue statements to find the enclosing loop.
+            (setf body (js2-parse-statement)
+                  (js2-loop-node-body pn) body
+                  (js2-node-pos pn) for-pos
+                  (js2-node-len pn) (- (js2-node-end body) for-pos))
+            (js2-node-add-children pn init cond incr body))
+        ;; finally
+        (js2-exit-loop))
+      (js2-pop-scope))
     pn))
 
 (defun js2-parse-try ()
@@ -7828,7 +7824,7 @@
         finally-block
         saw-default-catch
         peek
-        var-name
+        param
         catch-cond
         catch-node
         guard-kwd
@@ -7856,8 +7852,20 @@
             (js2-report-error "msg.catch.unreachable"))
         (if (js2-must-match js2-LP "msg.no.paren.catch")
             (setq lp (- js2-token-beg catch-pos)))
-        (js2-must-match js2-NAME "msg.bad.catchcond")
-        (setq var-name (js2-create-name-node))
+        (js2-push-scope (make-js2-scope))
+        (let ((tt (js2-peek-token)))
+          (cond
+           ;; destructuring pattern
+           ;;     catch ({ message, file }) { ... }
+           ((or (= tt js2-LB) (= tt js2-LC))
+            (setq param (js2-parse-destruct-primary-expr))
+            (js2-define-destruct-symbols param js2-LET nil))
+           ;; simple name
+           (t
+            (js2-must-match js2-NAME "msg.bad.catchcond")
+            (setq param (js2-create-name-node))
+            (js2-define-symbol js2-LET js2-ts-string param))))
+        ;; pattern guard
         (if (js2-match-token js2-IF)
             (setq guard-kwd (- js2-token-beg catch-pos)
                   catch-cond (js2-parse-expr))
@@ -7868,17 +7876,18 @@
         (setq block (js2-parse-statements)
               try-end (js2-node-end block)
               catch-node (make-js2-catch-node :pos catch-pos
-                                              :var-name var-name
+                                              :param param
                                               :guard-expr catch-cond
                                               :guard-kwd guard-kwd
                                               :block block
                                               :lp lp
                                               :rp rp))
+        (js2-pop-scope)
         (if (js2-must-match js2-RC "msg.no.brace.after.body")
             (setq try-end js2-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 var-name catch-cond block)
+        (js2-node-add-children catch-node param catch-cond block)
         (push catch-node catch-blocks)))
      ((/= peek js2-FINALLY)
       (js2-must-match js2-FINALLY "msg.try.no.catchfinally"
@@ -7907,8 +7916,7 @@
 (defun js2-parse-throw ()
   "Parser for throw-statement.  Last matched token must be js2-THROW."
   (let ((pos js2-token-beg)
-        expr
-        pn)
+        expr pn)
     (js2-consume-token)
     (if (= (js2-peek-token-or-eol) js2-EOL)
         ;; ECMAScript does not allow new lines before throw expression,
@@ -7921,7 +7929,7 @@
     (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."
@@ -8017,8 +8025,7 @@
 Last matched token must be js2-CONST or js2-VAR."
   (let ((tt (js2-peek-token))
         (pos js2-token-beg)
-        expr
-        pn)
+        expr pn)
     (js2-consume-token)
     (setq expr (js2-parse-variables tt js2-token-beg)
           pn (make-js2-expr-stmt-node :pos pos
@@ -8027,7 +8034,7 @@
     (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)
@@ -8042,8 +8049,7 @@
   "Parser for let-statement.  Last matched token must be js2-LET."
   (js2-consume-token)
   (let ((pos js2-token-beg)
-        expr
-        pn)
+        expr pn)
     (if (= (js2-peek-token) js2-LP)
         ;; let expression in statement context
         (setq expr (js2-parse-let pos 'statement)
@@ -8073,9 +8079,7 @@
         (end js2-token-end)
         (before js2-end-flags)
         (inside-function (js2-inside-function))
-        e
-        ret
-        name)
+        e ret name)
     (unless inside-function
       (js2-report-error (if (eq tt js2-RETURN)
                             "msg.bad.return"
@@ -8135,7 +8139,7 @@
 
 (defun js2-parse-block ()
   "Parser for a curly-delimited statement block.
-Last token matched must be js2-LC."
+Last token matched must be `js2-LC'."
   (let ((pos js2-token-beg)
         (pn (make-js2-scope)))
     (js2-consume-token)
@@ -8148,10 +8152,10 @@
       (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 js-SEMI or js-ERROR."
+Last matched token is `js2-SEMI' or `js2-ERROR'."
   (let ((tt (js2-peek-token)) pos len)
     (js2-consume-token)
     (if (eq tt js2-SEMI)
@@ -8215,10 +8219,7 @@
 expression and return it wrapped in a `js2-expr-stmt-node'."
   (let ((pos js2-token-beg)
         (end js2-token-end)
-        expr
-        stmt
-        pn
-        bundle
+        expr stmt pn bundle
         (continue t))
     ;; set check for label and call down to `js2-parse-primary-expr'
     (js2-set-check-for-label)
@@ -8263,25 +8264,19 @@
 DECL-TYPE is a token value: either VAR, CONST, or LET depending on context.
 For 'var' or 'const', the keyword should be the token last scanned.
 
-POS is the position where the node should start.  It's sometimes the
+POS is the position where the node should start. It's sometimes the
 var/const/let keyword, and other times the beginning of the first token
 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];
     ;; var {b: s2, a: s1} = foo, x = 6, y, [s3, s4] = bar;
+    ;; var {a, b} = baz;
     (while continue
       (setq destructuring nil
             name nil
@@ -8291,7 +8286,7 @@
             init nil)
       (if (or (= tt js2-LB) (= tt js2-LC))
           ;; Destructuring assignment, e.g., var [a, b] = ...
-          (setq destructuring (js2-parse-primary-expr)
+          (setq destructuring (js2-parse-destruct-primary-expr)
                 end (js2-node-end destructuring))
         ;; Simple variable name
         (when (js2-must-match js2-NAME "msg.bad.var")
@@ -8303,10 +8298,7 @@
       (when (js2-match-token js2-ASSIGN)
         (setq init (js2-parse-assign-expr)
               end (js2-node-end init))
-        (if (and js2-parse-ide-mode
-                 (or (js2-object-node-p init)
-                     (js2-function-node-p init)))
-            (js2-record-imenu-functions init name)))
+        (js2-record-imenu-functions init name))
       (when name
         (js2-set-face nbeg nend (if (js2-function-node-p init)
                                     'font-lock-function-name-face
@@ -8319,6 +8311,9 @@
           (progn
             (if (and (null init) (not js2-in-for-init))
                 (js2-report-error "msg.destruct.assign.no.init"))
+            (js2-define-destruct-symbols destructuring
+                                         decl-type
+                                         'font-lock-variable-name-face)
             (setf (js2-var-init-node-target vi) destructuring))
         (setf (js2-var-init-node-target vi) name))
       (setf (js2-var-init-node-initializer vi) init)
@@ -8364,8 +8359,8 @@
       (js2-pop-scope))
     pn))
 
-(defsubst js2-define-new-symbol (decl-type name node)
-  (js2-scope-put-symbol js2-current-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)))
 
@@ -8405,7 +8400,8 @@
               (js2-add-strict-warning "msg.var.redecl" name)
             (if (and js2-strict-var-hides-function-arg-warning (= sdt js2-LP))
                 (js2-add-strict-warning "msg.var.hides.arg" name)))
-        (js2-define-new-symbol decl-type name node)))
+        (js2-define-new-symbol decl-type name node
+                               js2-current-script-or-fn)))
      ((= decl-type js2-LP)
       (if symbol
           ;; must be duplicate parameter. Second parameter hides the
@@ -8414,13 +8410,14 @@
       (js2-define-new-symbol decl-type name node))
      (t (js2-code-bug)))))
 
-(defun js2-parse-expr ()
+(defun js2-parse-expr (&optional oneshot)
   (let* ((pn (js2-parse-assign-expr))
          (pos (js2-node-pos pn))
          left
          right
          op-pos)
-    (while (js2-match-token js2-COMMA)
+    (while (and (not oneshot)
+                (js2-match-token js2-COMMA))
       (setq op-pos (- js2-token-beg pos))  ; relative
       (if (= (js2-peek-token) js2-YIELD)
           (js2-report-error "msg.yield.parenthesized"))
@@ -8438,10 +8435,7 @@
 (defun js2-parse-assign-expr ()
   (let ((tt (js2-peek-token))
         (pos js2-token-beg)
-        pn
-        left
-        right
-        op-pos)
+        pn left right op-pos)
     (if (= tt js2-YIELD)
         (js2-parse-return-or-yield tt t)
       ;; not yield - parse assignment expression
@@ -8449,6 +8443,7 @@
             tt (js2-peek-token))
       (when (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
@@ -8461,9 +8456,7 @@
                                        :right right))
         (when js2-parse-ide-mode
           (js2-highlight-assign-targets pn left right)
-          (if (or (js2-function-node-p right)
-                  (js2-object-node-p right))
-              (js2-record-imenu-functions right left)))
+          (js2-record-imenu-functions right left))
         ;; do this last so ide checks above can use absolute positions
         (js2-node-add-children pn left right))
       pn)))
@@ -8630,7 +8623,7 @@
         (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)."
@@ -8656,7 +8649,7 @@
   (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)
@@ -8721,11 +8714,7 @@
 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)))
@@ -8785,14 +8774,7 @@
 
 (defun js2-parse-member-expr (&optional allow-call-syntax)
   (let ((tt (js2-peek-token))
-        pn
-        pos
-        target
-        args
-        beg
-        end
-        init
-        tail)
+        pn pos target args beg end init tail)
     (if (/= tt js2-NEW)
         (setq pn (js2-parse-primary-expr))
       ;; parse a 'new' expression
@@ -8821,7 +8803,7 @@
               end (js2-node-end init)
               (js2-new-node-initializer pn) init)
         (js2-node-add-children pn init))
-        (setf (js2-node-len pn) (- beg pos)))  ; end outer if
+        (setf (js2-node-len pn) (- end beg)))  ; end outer if
     (js2-parse-member-expr-tail allow-call-syntax pn)))
 
 (defun js2-parse-member-expr-tail (allow-call-syntax pn)
@@ -8855,9 +8837,7 @@
   "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)
+        op-pos expr end)
     (js2-consume-token)
     (js2-must-have-xml)
     (js2-set-requires-activation)
@@ -8882,8 +8862,7 @@
 Last token parsed must be `js2-RB'."
   (let ((lb js2-token-beg)
         (pos (js2-node-pos pn))
-        rb
-        expr)
+        rb expr)
     (js2-consume-token)
     (setq expr (js2-parse-expr))
     (if (js2-must-match js2-RB "msg.no.bracket.index")
@@ -9014,10 +8993,7 @@
   (let ((pos (or at-pos js2-token-beg))
         colon-pos
         (name (js2-create-name-node t js2-current-token))
-        ns
-        tt
-        ref
-        pn)
+        ns tt ref pn)
     (catch 'return
       (when (js2-match-token js2-COLONCOLON)
         (setq ns name
@@ -9071,6 +9047,10 @@
                                           :rb (js2-relpos rb pos)))
       (js2-node-add-children pn namespace expr))))
 
+(defun js2-parse-destruct-primary-expr ()
+  (let ((js2-is-in-destructuring t))
+    (js2-parse-primary-expr)))
+
 (defun js2-parse-primary-expr ()
   "Parse a literal (leaf) expression of some sort.
 Includes complex literals such as functions, object-literals,
@@ -9124,7 +9104,7 @@
                                 :value js2-ts-string
                                 :flags flags)
         (js2-set-face px-pos js2-ts-cursor 'font-lock-string-face 'record)
-        (put-text-property px-pos js2-ts-cursor 'syntax-table '(2))))
+        (js2-record-text-property px-pos js2-ts-cursor 'syntax-table '(2))))
      ((or (= tt js2-NULL)
           (= tt js2-THIS)
           (= tt js2-FALSE)
@@ -9147,28 +9127,32 @@
 
 (defun js2-parse-name (tt-flagged tt)
   (let ((name js2-ts-string)
-        (name-pos js2-token-beg))
-      (if (and (js2-flag-set-p tt-flagged js2-ti-check-label)
-               (= (js2-peek-token) js2-COLON))
-          (prog1
+        (name-pos js2-token-beg)
+        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)
-        (if js2-compiler-xml-available
-            (js2-parse-property-name nil name 0)
-          (js2-create-name-node 'check-activation)))))
+          (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)
+(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
@@ -9185,11 +9169,10 @@
   (let ((pos js2-token-beg)
         (end js2-token-end)
         (after-lb-or-comma t)
-        after-comma
-        tt
-        elems
-        pn
+        after-comma tt elems pn
         (continue t))
+    (unless js2-is-in-destructuring
+        (js2-push-scope (make-js2-scope))) ; for array comp
     (while continue
       (setq tt (js2-peek-token))
       (cond
@@ -9212,9 +9195,23 @@
                                       :len (- js2-ts-cursor pos)
                                       :elems (nreverse elems)))
         (apply #'js2-node-add-children pn (js2-array-node-elems pn))
-        (when after-comma
+        (when (and after-comma (not js2-is-in-destructuring))
           (js2-parse-warn-trailing-comma "msg.array.trailing.comma"
                                          pos elems after-comma)))
+       ;; destructuring binding
+       (js2-is-in-destructuring
+        (push (if (or (= tt js2-LC)
+                      (= tt js2-LB)
+                      (= tt js2-NAME))
+                  ;; [a, b, c] | {a, b, c} | {a:x, b:y, c:z} | a
+                  (js2-parse-destruct-primary-expr)
+                ;; invalid pattern
+                (js2-consume-token)
+                (js2-report-error "msg.bad.var")
+                (make-js2-error-node))
+              elems)
+        (setq after-lb-or-comma nil
+              after-comma nil))
        ;; array comp
        ((and (>= js2-language-version 170)
              (= tt js2-FOR)          ; check for array comprehension
@@ -9223,6 +9220,7 @@
              (not (cdr elems)))      ; but no 2nd element
         (setf continue nil
               pn (js2-parse-array-comprehension (car elems) pos)))
+
        ;; another element
        (t
         (unless after-lb-or-comma
@@ -9230,6 +9228,8 @@
         (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)
@@ -9237,12 +9237,17 @@
 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
-        filter
-        if-pos
-        result)
+  (let (loops loop first prev filter if-pos result)
     (while (= (js2-peek-token) js2-FOR)
-      (push (js2-parse-array-comp-loop) loops))
+      (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
@@ -9258,21 +9263,15 @@
                                            :if-pos if-pos))
     (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
     result))
 
 (defun js2-parse-array-comp-loop ()
-  "Parse a 'for [each] (foo in bar)' expression in an Array comprehension.
+  "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
-        in-pos
-        each-pos
-        lp
-        rp)
+        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)
     (unwind-protect
@@ -9290,10 +9289,10 @@
           (cond
            ((or (= tt js2-LB)
                 (= tt js2-LC))
-            ;; handle destructuring assignment
-            (setq iter (js2-parse-primary-expr)))
-           ((js2-valid-prop-name-token tt)
-            (js2-consume-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")))
@@ -9301,8 +9300,12 @@
           ;; 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 (js2-must-match js2-IN "msg.in.after.for.name")
-              (setq in-pos (- js2-token-beg pos)))
+          (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)))
@@ -9313,6 +9316,7 @@
                 (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))
@@ -9321,15 +9325,13 @@
 
 (defun js2-parse-object-literal ()
   (let ((pos js2-token-beg)
-        tt
-        elems
-        result
-        after-comma
+        tt elems result after-comma
         (continue t))
     (while continue
       (setq tt (js2-peek-token))
       (cond
-       ;; {foo: ...}, {'foo': ...}, {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
@@ -9364,7 +9366,8 @@
     result))
 
 (defun js2-parse-named-prop (tt)
-  "Parse a name, string, or getter/setter object property."
+  "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)))
@@ -9373,18 +9376,33 @@
         (pend js2-token-end)
         (name (js2-create-name-node))
         (prop js2-ts-string))
-    (if (and (= tt js2-NAME)
-             (= (js2-peek-token) js2-NAME)
-             (or (string= prop "get")
-                 (string= prop "set")))
-        (progn
-          ;; getter/setter prop
-          (js2-consume-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
-          (js2-parse-getter-setter-prop ppos name (string= prop "get")))
-      ;; regular prop
+    (cond
+     ;; getter/setter prop
+     ((and (= tt js2-NAME)
+           (= (js2-peek-token) js2-NAME)
+           (or (string= prop "get")
+               (string= prop "set")))
+      (js2-consume-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
+      (js2-parse-getter-setter-prop ppos name (string= prop "get")))
+     ;; Abbreviated destructuring binding, e.g. {a, b} = c;
+     ;; XXX: To be honest, the value of `js2-is-in-destructuring' becomes t 
only
+     ;; when patterns are used in variable declarations, function parameters,
+     ;; catch-clause, and iterators.
+     ;; We have to set `js2-is-in-destructuring' to t when the current
+     ;; expressions are on the left side of any assignment, but it's difficult
+     ;; because it requires looking ahead of expression.
+     ((and js2-is-in-destructuring
+           (= tt js2-NAME)
+           (let ((ctk (js2-peek-token)))
+             (or (= ctk js2-COMMA)
+                 (= ctk js2-RC)
+                 (js2-valid-prop-name-token ctk))))
+      name)
+     ;; regular prop
+     (t
       (prog1
           (setq expr (js2-parse-plain-property (or string-prop name)))
         (js2-set-face ppos pend
@@ -9392,7 +9410,7 @@
                            (js2-object-prop-node-right expr))
                           'font-lock-function-name-face
                         'font-lock-variable-name-face)
-                      'record)))))
+                      'record))))))
 
 (defun js2-parse-plain-property (prop)
   "Parse a non-getter/setter property in an object literal.
@@ -9419,12 +9437,15 @@
 
   { get foo() {...}, set foo(x) {...} }
 
+and expression closure style is also supported
+
+  { get foo() x, set foo(x) _x = x }
+
 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
+        result end
         (fn (js2-parse-function 'FUNCTION_EXPRESSION)))
     ;; it has to be an anonymous function, as we already parsed the name
     (if (/= (js2-node-type fn) js2-FUNCTION)
@@ -9490,19 +9511,23 @@
 ;; Karl for coming up with the initial approach, which packs a lot of
 ;; punch for so little code.
 
-(defconst js-possibly-braceless-keyword-re
-  (regexp-opt
-   '("catch" "do" "else" "finally" "for" "if" "try" "while" "with" "let")
-   'words)
+(defconst js2-possibly-braceless-keywords-re
+  (concat "else[ \t]+if\\|for[ \t]+each\\|"
+          (regexp-opt '("catch" "do" "else" "finally" "for" "if"
+                        "try" "while" "with" "let")))
   "Regular expression matching keywords that are optionally
 followed by an opening brace.")
 
-(defconst js-indent-operator-re
+(defconst js2-indent-operator-re
   (concat "[-+*/%<>=&^|?:.]\\([^-+*/]\\|$\\)\\|"
           (regexp-opt '("in" "instanceof") 'words))
   "Regular expression matching operators that affect indentation
 of continued expressions.")
 
+(defconst js2-declaration-keyword-re
+  (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.
@@ -9511,7 +9536,7 @@
   nil)
 
 (defun js2-insert-and-indent (key)
-  "Run command bound to key and indent current line.
+  "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)))
@@ -9520,22 +9545,23 @@
         (call-interactively cmd)))
   ;; don't do the electric keys inside comments or strings,
   ;; and don't do bounce-indent with them.
-  (let ((parse-state (parse-partial-sexp (point-min) (point)))
+  (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 js-re-search-forward-inner (regexp &optional bound count)
-  "Auxiliary function for `js-re-search-forward'."
-  (let ((parse)
-        (saved-point (point-min)))
+(defun js2-re-search-forward-inner (regexp &optional bound count)
+  "Auxiliary function for `js2-re-search-forward'."
+  (let (parse saved-point)
     (while (> count 0)
       (re-search-forward regexp bound)
-      (setq parse (parse-partial-sexp saved-point (point)))
+      (setq parse (if saved-point
+                      (parse-partial-sexp saved-point (point))
+                    (syntax-ppss (point))))
       (cond ((nth 3 parse)
-             (re-search-forward 
-              (concat "\\([^\\]\\|^\\)" (string (nth 3 parse))) 
+             (re-search-forward
+              (concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
               (save-excursion (end-of-line) (point)) t))
             ((nth 7 parse)
              (forward-line))
@@ -9547,18 +9573,18 @@
       (setq saved-point (point))))
   (point))
 
-(defun js-re-search-forward (regexp &optional bound noerror count)
+(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."
+Invokes `re-search-forward' but treats the buffer as if strings
+and comments have been removed."
   (let ((saved-point (point))
-        (search-expr 
+        (search-expr
          (cond ((null count)
-                '(js-re-search-forward-inner regexp bound 1))
+                '(js2-re-search-forward-inner regexp bound 1))
                ((< count 0)
-                '(js-re-search-backward-inner regexp bound (- count)))
+                '(js2-re-search-backward-inner regexp bound (- count)))
                ((> count 0)
-                '(js-re-search-forward-inner regexp bound count)))))
+                '(js2-re-search-forward-inner regexp bound count)))))
     (condition-case err
         (eval search-expr)
       (search-failed
@@ -9566,18 +9592,17 @@
        (unless noerror
          (error (error-message-string err)))))))
 
-(defun js-re-search-backward-inner (regexp &optional bound count)
-  "Auxiliary function for `js-re-search-backward'."
-  (let ((parse)
-        (saved-point (point-min)))
+(defun js2-re-search-backward-inner (regexp &optional bound count)
+  "Auxiliary function for `js2-re-search-backward'."
+  (let (parse)
     (while (> count 0)
       (re-search-backward regexp bound)
-      (setq parse (parse-partial-sexp saved-point (point)))
+      (setq parse (syntax-ppss (point)))
       (cond ((nth 3 parse)
              (re-search-backward
-              (concat "\\([^\\]\\|^\\)" (string (nth 3 parse))) 
+              (concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
               (line-beginning-position) t))
-            ((nth 7 parse) 
+            ((nth 7 parse)
              (goto-char (nth 8 parse)))
             ((or (nth 4 parse)
                  (and (eq (char-before) ?/) (eq (char-after) ?*)))
@@ -9586,18 +9611,18 @@
              (setq count (1- count))))))
   (point))
 
-(defun js-re-search-backward (regexp &optional bound noerror count)
+(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."
+Invokes `re-search-backward' but treats the buffer as if strings
+and comments have been removed."
   (let ((saved-point (point))
-        (search-expr 
+        (search-expr
          (cond ((null count)
-                '(js-re-search-backward-inner regexp bound 1))
+                '(js2-re-search-backward-inner regexp bound 1))
                ((< count 0)
-                '(js-re-search-forward-inner regexp bound (- count)))
+                '(js2-re-search-forward-inner regexp bound (- count)))
                ((> count 0)
-                '(js-re-search-backward-inner regexp bound count)))))
+                '(js2-re-search-backward-inner regexp bound count)))))
     (condition-case err
         (eval search-expr)
       (search-failed
@@ -9605,89 +9630,123 @@
        (unless noerror
          (error (error-message-string err)))))))
 
-(defun js-looking-at-operator-p ()
-  "Return non-nil if text after point is an operator (that is not
-a comma)."
-  (and (looking-at js-indent-operator-re)
+(defun js2-looking-at-operator-p ()
+  "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 (js-re-search-backward "[?:{]\\|\\<case\\>" nil t)
+             (and (js2-re-search-backward "[?:{]\\|\\<case\\>" nil t)
                   (looking-at "?"))))))
 
-(defun js-continued-expression-p ()
+(defun js2-continued-expression-p ()
   "Return non-nil if the current line continues an expression."
   (save-excursion
     (back-to-indentation)
-    (or (js-looking-at-operator-p)
-        (when (js-re-search-backward "\n" nil t)
-          (skip-chars-backward " \t")
+    (or (js2-looking-at-operator-p)
+        (when (catch 'found
+                (while (and (re-search-backward "\n" nil t)
+                            (let ((state (syntax-ppss)))
+                              (when (nth 4 state)
+                                (goto-char (nth 8 state))) ;; skip comments
+                              (skip-chars-backward " \t")
+                              (if (bolp)
+                                  t
+                                (throw 'found t))))))
           (backward-char)
-          (when (js-looking-at-operator-p)
+          (when (js2-looking-at-operator-p)
             (backward-char)
             (not (looking-at "\\*\\|++\\|--\\|/[/*]")))))))
 
-(defun js-end-of-do-while-loop-p ()
+(defun js2-end-of-do-while-loop-p ()
   "Return non-nil if word after point is `while' of a do-while
-statement, else returns nil.  A braceless do-while statement
+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
     (when (looking-at "\\s-*\\<while\\>")
-      (if (save-excursion 
+      (if (save-excursion
             (skip-chars-backward "[ \t\n]*}")
             (looking-at "[ \t\n]*}"))
-          (save-excursion 
+          (save-excursion
             (backward-list) (backward-word 1) (looking-at "\\<do\\>"))
-        (js-re-search-backward "\\<do\\>" (point-at-bol) t)
+        (js2-re-search-backward "\\<do\\>" (point-at-bol) t)
         (or (looking-at "\\<do\\>")
             (let ((saved-indent (current-indentation)))
-              (while (and (js-re-search-backward "^[ \t]*\\<" nil t)
+              (while (and (js2-re-search-backward "^[ \t]*\\<" nil t)
                           (/= (current-indentation) saved-indent)))
               (and (looking-at "[ \t]*\\<do\\>")
-                   (not (js-re-search-forward 
+                   (not (js2-re-search-forward
                          "\\<while\\>" (point-at-eol) t))
                    (= (current-indentation) saved-indent))))))))
 
-(defun js-ctrl-statement-indentation ()
-  "Return 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
-    (save-excursion
-      (back-to-indentation)
-      (when (save-excursion
-              (and (not (js2-same-line (point-min)))
-                   (not (looking-at "{"))
-                   (js-re-search-backward "[[:graph:]]" nil t)
-                   (not (looking-at "[{([]"))
-                   (progn
-                     (forward-char) 
+(defun js2-multiline-decl-indentation ()
+  "Returns 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)
+    (save-excursion
+      (back-to-indentation)
+      (when (not (looking-at js2-declaration-keyword-re))
+        (when (looking-at js2-indent-operator-re)
+          (goto-char (match-end 0))) ; continued expressions are ok
+        (while (and (not at-opening-bracket)
+                    (not (bobp))
+                    (let ((pos (point)))
+                      (save-excursion
+                        (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) ?\;))))
+                            (js2-same-line pos)))))
+          (condition-case err
+              (backward-sexp)
+            (scan-error (setq at-opening-bracket t))))
+        (when (looking-at js2-declaration-keyword-re)
+          (goto-char (match-end 0))
+          (1+ (current-column)))))))
+
+(defun js2-ctrl-statement-indentation ()
+  "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)))
+                 (not (looking-at "{"))
+                 (js2-re-search-backward "[[:graph:]]" nil t)
+                 (not (looking-at "[{([]"))
+                 (progn
+                   (forward-char)
+                   (when (= (char-before) ?\))
                      ;; scan-sexps sometimes throws an error
                      (ignore-errors (backward-sexp))
-                     (when (looking-at "(") (backward-word 1))
-                     (and (save-excursion
-                            (skip-chars-backward " \t}" (point-at-bol))
-                            (bolp))
-                          (looking-at js-possibly-braceless-keyword-re)
-                          (save-match-data
-                            (not (js-end-of-do-while-loop-p)))))))
-        (save-excursion
-          (goto-char (match-beginning 0))
-          (+ (current-indentation) js2-basic-offset))))))
+                     (skip-chars-backward " \t" (point-at-bol)))
+                   (let ((pt (point)))
+                     (back-to-indentation)
+                     (and (looking-at js2-possibly-braceless-keywords-re)
+                          (= (match-end 0) pt)
+                          (not (js2-end-of-do-while-loop-p))))))
+        (+ (current-indentation) js2-basic-offset)))))
 
 (defun js2-indent-in-array-comp (parse-status)
   "Return non-nil if we think we're in an array comprehension.
 In particular, return the buffer position of the first `for' kwd."
-  (let ((end (point)))
-    (when (nth 1 parse-status)
+  (let ((bracket (nth 1 parse-status))
+        (end (point)))
+    (when bracket
       (save-excursion
-        (goto-char (nth 1 parse-status))
+        (goto-char bracket)
         (when (looking-at "\\[")
           (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
@@ -9695,7 +9754,11 @@
                     (match-beginning 0)))
             ;; to skip arbitrary expressions we need the parser,
             ;; so we'll just guess at it.
-            (if (re-search-forward "[^,]* \\(for\\) " end t)
+            (if (and (> end (point)) ; not empty literal
+                     (re-search-forward "[^,]]* \\(for\\) " end t)
+                     ;; not inside comment or string literal
+                     (let ((state (parse-partial-sexp bracket (point))))
+                       (not (or (nth 3 state) (nth 4 state)))))
                 (match-beginning 1))))))))
 
 (defun js2-array-comp-indentation (parse-status for-kwd)
@@ -9709,19 +9772,22 @@
     (save-excursion
       (goto-char for-kwd)
       (current-column))))
-      
-(defun js-proper-indentation (parse-status)
+
+(defun js2-proper-indentation (parse-status)
   "Return the proper indentation for the current line."
   (save-excursion
     (back-to-indentation)
-    (let ((ctrl-stmt-indent (js-ctrl-statement-indentation))
+    (let ((ctrl-stmt-indent (js2-ctrl-statement-indentation))
           (same-indent-p (looking-at "[]})]\\|\\<case\\>\\|\\<default\\>"))
-          (continued-expr-p (js-continued-expression-p))
+          (continued-expr-p (js2-continued-expression-p))
+          (declaration-indent (and js2-pretty-multiline-declarations
+                                   (js2-multiline-decl-indentation)))
           (bracket (nth 1 parse-status))
           beg)
       (cond
        ;; indent array comprehension continuation lines specially
        ((and bracket
+             (>= js2-language-version 170)
              (not (js2-same-line bracket))
              (setq beg (js2-indent-in-array-comp parse-status))
              (>= (point) (save-excursion
@@ -9731,24 +9797,28 @@
 
        (ctrl-stmt-indent)
 
+       ((and declaration-indent continued-expr-p)
+        (+ declaration-indent js2-basic-offset))
+
+       (declaration-indent)
+
        (bracket
         (goto-char bracket)
         (cond
          ((looking-at "[({[][ \t]*\\(/[/*]\\|$\\)")
-          (let ((p (parse-partial-sexp (point-at-bol) (point))))
-            (when (save-excursion (skip-chars-backward " \t)")
-                                  (looking-at ")"))
-              (backward-list))
-            (if (nth 1 p)
-                (progn (goto-char (1+ (nth 1 p)))
-                       (skip-chars-forward " \t"))
-              (back-to-indentation))
-            (cond (same-indent-p
-                   (current-column))
-                  (continued-expr-p
-                   (+ (current-column) (* 2 js2-basic-offset)))
-                  (t
-                   (+ (current-column) js2-basic-offset)))))
+          (when (save-excursion (skip-chars-backward " \t)")
+                                (looking-at ")"))
+            (backward-list))
+          (back-to-indentation)
+          (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))))
          (t
           (unless same-indent-p
             (forward-char)
@@ -9756,6 +9826,7 @@
           (current-column))))
 
        (continued-expr-p js2-basic-offset)
+
        (t 0)))))
 
 (defun js2-lineup-comment (parse-status)
@@ -9780,7 +9851,7 @@
   (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
@@ -9789,14 +9860,14 @@
     (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-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 ":")))
@@ -9813,11 +9884,11 @@
      (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)
@@ -9828,15 +9899,15 @@
            (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")
-    (looking-at-p "case\\s-.+:")))
+    (looking-at "case\\s-.+:")))
 
-(defsubst js2-syntax-bol ()
+(defun 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
@@ -9844,32 +9915,27 @@
     (skip-chars-forward " \t")
     (point)))
 
-(defun js2-bounce-indent (normal-col parse-status)
+(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
 column computed by the heuristic guesser based on current paren,
-bracket, brace and statement nesting."
+bracket, brace and statement nesting.  If BACKWARDS, cycle positions
+in reverse."
   (let ((cur-indent (js2-current-indent))
         (old-buffer-undo-list buffer-undo-list)
         ;; Emacs 21 only has `count-lines', not `line-number-at-pos'
         (current-line (save-excursion
                         (forward-line 0)  ; move to bol
                         (1+ (count-lines (point-min) (point)))))
-        positions
-        pos
-        anchor
-        arglist-cont
-        same-indent
-        prev-line-col
-        basic-offset
-        computed-pos)
+        positions pos main-pos anchor arglist-cont same-indent
+        prev-line-col 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)
@@ -9879,11 +9945,8 @@
                          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)
@@ -9893,7 +9956,7 @@
                          (* 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
@@ -9906,10 +9969,9 @@
                         (current-column))))
           (when pos
             (incf pos js2-basic-offset)
-            (unless (member pos positions)
-              (push pos positions)))
+            (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
@@ -9931,12 +9993,12 @@
                          (js2-indent-in-objlit-p parse-status))
                      (not (js2-arglist-close)))
                 (setq same-indent pos))
-            (unless (member pos positions)
-              (push pos positions)))
+            (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)
                       (js2-backward-sws)
                       (back-to-indentation)
                       (setq anchor (current-column))
@@ -9946,28 +10008,30 @@
                                         (current-column))
                                       anchor)))
                       (setq pos (current-column))))
-          (unless (member pos positions)
-            (push pos positions))
-
-          ;; put nesting-heuristic position first in list, sort rest
-          (setq positions (nreverse (sort positions '<)))
-          (setq positions (cons normal-col (delete normal-col positions)))
+          (push pos positions)
+
+          ;; nesting-heuristic position, main by default
+          (push (setq main-pos normal-col) positions)
+
+          ;; delete duplicates and sort positions list
+          (setq positions (sort (delete-dups positions) '<))
 
           ;; comma-list continuation lines:  prev line indent takes precedence
           (if same-indent
-              (setq positions
-                    (cons same-indent
-                          (sort (delete same-indent positions) '<))))
+              (setq main-pos same-indent))
 
           ;; common special cases where we want to indent in from previous line
           (if (or (js2-indent-case-block-p)
                   (js2-indent-objlit-arg-p parse-status))
-              (setq positions
-                    (cons basic-offset
-                          (delete basic-offset positions))))
+              (setq main-pos basic-offset))
+
+          ;; if bouncing backwards, reverse positions list
+          (if backwards
+              (setq positions (reverse positions)))
 
           ;; record whether we're already sitting on one of the alternatives
           (setq pos (member cur-indent positions))
+
           (cond
            ;; case 0:  we're one one of the alternatives and this is the
            ;; first time they've pressed TAB on this line (best-guess).
@@ -9982,7 +10046,7 @@
             (setq computed-pos 0))
            ;; case 2:  not on any of the computed spots => use main spot
            ((not pos)
-            (setq computed-pos 0))
+            (setq computed-pos (js2-position main-pos positions)))
            ;; case 3:  on last position:  cycle to first position
            ((null (cdr pos))
             (setq computed-pos 0))
@@ -10005,8 +10069,14 @@
           (setq buffer-undo-list old-buffer-undo-list))
       ;; see commentary for `js2-mode-last-indented-line'
       (setq js2-mode-last-indented-line current-line))))
-      
-(defsubst js2-1-line-comment-continuation-p ()
+
+(defun js2-indent-bounce-backwards ()
+  "Calls `js2-indent-line'.  When `js2-bounce-indent-p',
+cycles between the computed indentation positions in reverse order."
+  (interactive)
+  (js2-indent-line t))
+
+(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
@@ -10020,52 +10090,205 @@
              (js2-backward-sws)
              (forward-line 0))
            (looking-at "\\s-*//")))))
-       
-(defun js2-indent-line ()
+
+(defun js2-indent-line (&optional bounce-backwards)
   "Indent the current line as JavaScript source text."
   (interactive)
-  (let (parse-status
-        current-indent
-        offset
-        indent-col
-        moved
-        ;; don't whine about errors/warnings when we're indenting.
+  (let (parse-status current-indent offset indent-col moved
+        ;; 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
                          (syntax-ppss (point-at-bol)))
           offset (- (point) (save-excursion
                               (back-to-indentation)
-                              (setq current-indent (current-column))
                               (point))))
     (js2-with-underscore-as-word-syntax
      (if (nth 4 parse-status)
          (js2-lineup-comment parse-status)
-       (setq indent-col (js-proper-indentation parse-status))
-       ;; see comments below about js2-mode-last-indented-line
-       (when 
-           (cond
-            ;; bounce-indenting is disabled during electric-key indent.
-            ;; It doesn't work well on first line of buffer.
-            ((and js2-bounce-indent-p
-                  (not (js2-same-line (point-min)))
-                  (not (js2-1-line-comment-continuation-p)))
-             (js2-bounce-indent indent-col parse-status)
-             (setq moved t))
-            ;; just indent to the guesser's likely spot
-            ((/= current-indent indent-col)
-             (indent-line-to indent-col)
-             (setq moved t)))
-         (when (and moved (plusp offset))
-           (forward-char offset)))))))
+       (setq indent-col (js2-proper-indentation parse-status))
+       ;; 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.
+        ((and js2-bounce-indent-p
+              (not (js2-same-line (point-min)))
+              (not (js2-1-line-comment-continuation-p)))
+         (js2-bounce-indent indent-col parse-status bounce-backwards))
+        ;; just indent to the guesser's likely spot
+        (t (indent-line-to indent-col))))
+     (when (plusp offset)
+       (forward-char offset)))))
 
 (defun js2-indent-region (start end)
   "Indent the region, but don't use bounce indenting."
   (let ((js2-bounce-indent-p nil)
-        (indent-region-function nil))
-    (indent-region start end nil)))  ; nil for byte-compiler
-
-;;;###autoload (add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
+        (indent-region-function nil)
+        (after-change-functions (remq 'js2-mode-edit
+                                      after-change-functions)))
+    (indent-region start end nil) ; nil for byte-compiler
+    (js2-mode-edit start end (- end start))))
+
+(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
+(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 `espresso-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)
+  (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)))
+  ;; 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)
+  (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)
+  (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)))
+
+(defun js2-error-buffer-prev ()
+  "Move to previous error and view it."
+  (interactive)
+  (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"
@@ -10077,19 +10300,11 @@
        (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'.  Still not perfect, though -- something to do
-  ;; with our binding of the RET key inside comments:  short lines stay short.
   (set (make-local-variable 'fill-paragraph-function) #'c-fill-paragraph)
   (set (make-local-variable 'comment-line-break-function) #'js2-line-break)
-
-  (set (make-local-variable 'before-save-hook) #'js2-before-save)
-  (set (make-local-variable 'next-error-function) #'js2-next-error)
   (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
@@ -10104,9 +10319,9 @@
        '((?\; . after) (?\{ . after) (?\} . before)))
 
   ;; some variables needed by cc-engine for paragraph-fill, etc.
-  (setq c-buffer-is-cc-mode t
-        c-comment-prefix-regexp js2-comment-prefix-regexp
+  (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
@@ -10114,44 +10329,38 @@
         c-syntactic-ws-end js2-syntactic-ws-end
         c-syntactic-eol js2-syntactic-eol)
 
+  (let ((c-buffer-is-cc-mode t))
+    ;; Copied from `js-mode'.  Also see Bug#6071.
+    (make-local-variable 'paragraph-start)
+    (make-local-variable 'paragraph-separate)
+    (make-local-variable 'paragraph-ignore-fill-prefix)
+    (make-local-variable 'adaptive-fill-mode)
+    (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).  We do this by not letting
-  ;; font-lock unfontify anything, and telling it to fontify after we
-  ;; re-parse and re-highlight the buffer.  (We currently don't do any
-  ;; work with regions other than the whole buffer.)
-  ;; FIXME: This is asking for trouble.
-  (dolist (var '(font-lock-unfontify-buffer-function
-                 font-lock-unfontify-region-function))
-    (set (make-local-variable var) (lambda (&rest args) t)))
-
-  ;; Don't let font-lock do syntactic (string/comment) fontification.
-  ;; FIXME: Better tell font-lock to not do syntactic fontification.
-  (set (make-local-variable #'font-lock-syntactic-face-function)
-       (lambda (state) nil))
+                (if js2-include-browser-externs js2-browser-externs)
+                (if js2-include-gears-externs js2-gears-externs)
+                (if js2-include-rhino-externs js2-rhino-externs)))
+
+  (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))
   (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)
+
   (setq js2-mode-functions-hidden nil
         js2-mode-comments-hidden nil
         js2-mode-buffer-dirty-p t
@@ -10172,66 +10381,49 @@
   (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))
   (setq js2-mode-parsing nil)
-  (setq js2-mode-parse-timer
-        (run-with-idle-timer js2-idle-timer-delay nil #'js2-reparse)))
+  (let ((timer (timer-create)))
+    (setq js2-mode-parse-timer timer)
+    (timer-set-function timer 'js2-mode-idle-reparse (list (current-buffer)))
+    (timer-set-idle-time timer js2-idle-timer-delay)
+    ;; http://debbugs.gnu.org/cgi/bugreport.cgi?bug=12326
+    (timer-activate-when-idle timer nil)))
+
+(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))))
+
+(defun js2-mode-idle-reparse-inner ()
+  (remove-hook 'window-configuration-change-hook
+               #'js2-mode-idle-reparse-inner
+               t)
+  (js2-reparse))
 
 (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-mode-run-font-lock ()
-  "Run `font-lock-fontify-buffer' after parsing/highlighting.
-This is intended to allow modes that install their own font-lock keywords
-to work with `js2-mode'.  In practice it never seems to work for long.
-Hopefully the Emacs maintainers can help figure out a way to make it work."
-  ;; FIXME: Throw away this function.
-  ;; <note from="Emacs maintainer">
-  ;; Just writing "hopefully the Emacs maintainers can help" in some file
-  ;; somewhere on the Internet is not enough because we don't
-  ;; have a web-crawler looking for such things.  Instead, it's best to ask
-  ;; explicitly on gnu.emacs.help or address@hidden or something.
-  ;; </note>
-  ;; As for the answer: set font-lock-defaults in js2-mode once and for all.
-  ;; It should have a value like '(js2-font-lock-keywords t) and
-  ;; js2-font-lock-keywords should have a value like '(js2-fontify-region).
-  (when (and (boundp 'font-lock-keywords)
-             font-lock-keywords
-             (boundp 'font-lock-mode)
-             font-lock-mode)
-    ;; TODO:  font-lock and jit-lock really really REALLY don't want to
-    ;; play nicely with js2-mode.  They go out of their way to fail to
-    ;; provide any option for saying "look, fontify the farging buffer
-    ;; with just the keywords already".  Argh.
-    (setq font-lock-defaults (list font-lock-keywords 'keywords-only))
-    (let (font-lock-verbose)
-      (font-lock-default-fontify-buffer))))
+(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))
 
 (defun js2-reparse (&optional force)
   "Re-parse current buffer after user finishes some data entry.
@@ -10247,11 +10439,9 @@
           (when (or js2-mode-buffer-dirty-p force)
             (js2-remove-overlays)
             (with-silent-modifications
-              (remove-text-properties (point-min) (point-max) '(syntax-table))
               (setq js2-mode-buffer-dirty-p nil
                     js2-mode-fontifications nil
-                    js2-mode-deferred-properties nil
-                    js2-additional-externs nil)
+                    js2-mode-deferred-properties nil)
               (if js2-mode-verbose-parse-p
                   (message "parsing..."))
               (setq time
@@ -10259,12 +10449,14 @@
                      (setq interrupted-p
                            (catch 'interrupted
                              (setq js2-mode-ast (js2-parse))
-                             (js2-mode-fontify-regions)
+                             ;; if parsing is interrupted, comments and regex
+                             ;; literals stay ignored by `parse-partial-sexp'
+                             (remove-text-properties (point-min) (point-max)
+                                                     '(syntax-table))
+                             (js2-mode-apply-deferred-properties)
                              (js2-mode-remove-suppressed-warnings)
                              (js2-mode-show-warnings)
                              (js2-mode-show-errors)
-                             (js2-mode-run-font-lock)  ; note:  doesn't work
-                             (js2-mode-highlight-magic-parens)
                              (if (>= js2-highlight-level 1)
                                  (js2-highlight-jsdoc js2-mode-ast))
                              nil))))
@@ -10279,13 +10471,14 @@
         (unless interrupted-p
           (setq js2-mode-parse-timer nil))))))
 
-(defun js2-mode-show-node ()
+(defun js2-mode-show-node (event)
   "Debugging aid:  highlight selected AST node on mouse click."
-  (interactive)
-  (let ((node (js2-node-at-point))
-        beg
-        end)
-    (when js2-mode-show-overlay
+  (interactive "e")
+  (mouse-set-point event)
+  (setq deactivate-mark t)
+  (when js2-mode-show-overlay
+    (let ((node (js2-node-at-point))
+          beg end)
       (if (null node)
           (message "No node found at location %s" (point))
         (setq beg (js2-node-abs-pos node)
@@ -10293,7 +10486,7 @@
         (if js2-mode-node-overlay
             (move-overlay js2-mode-node-overlay beg end)
           (setq js2-mode-node-overlay (make-overlay beg end))
-          (overlay-put js2-mode-node-overlay 'face 'highlight))
+          (overlay-put js2-mode-node-overlay 'font-lock-face 'highlight))
         (with-silent-modifications
           (put-text-property beg end 'point-left #'js2-mode-hide-overlay))
         (message "%s, parent: %s"
@@ -10323,9 +10516,10 @@
   (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)))
@@ -10334,7 +10528,7 @@
          (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 '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)))
@@ -10355,15 +10549,16 @@
         for o in (overlays-at pos)
         thereis (overlay-get o 'js2-error)))
 
-(defun js2-mode-fontify-regions ()
-  "Apply fontifications recorded during parsing."
-  ;; We defer clearing faces as long as possible to eliminate flashing.
-  (js2-clear-face (point-min) (point-max))
-  ;; have to reverse the recorded fontifications so that errors and
-  ;; warnings overwrite the normal fontifications
-  (dolist (f (nreverse js2-mode-fontifications))
-    (put-text-property (first f) (second f) 'face (third f)))
-  (setq js2-mode-fontifications nil)
+(defun js2-mode-apply-deferred-properties ()
+  "Apply fontifications and other text properties recorded during parsing."
+  (when (plusp js2-highlight-level)
+    ;; We defer clearing faces as long as possible to eliminate flashing.
+    (js2-clear-face (point-min) (point-max))
+    ;; Have to reverse the recorded fontifications list so that errors
+    ;; and warnings overwrite the normal fontifications.
+    (dolist (f (nreverse js2-mode-fontifications))
+      (put-text-property (first f) (second f) 'font-lock-face (third f)))
+    (setq js2-mode-fontifications nil))
   (dolist (p js2-mode-deferred-properties)
     (apply #'put-text-property p))
   (setq js2-mode-deferred-properties nil))
@@ -10411,53 +10606,56 @@
 (defun js2-echo-error (old-point new-point)
   "Called by point-motion hooks."
   (let ((msg (get-text-property new-point 'help-echo)))
-    (if msg
-        (message msg))))
+    (when (and (stringp msg) (or (not (current-message))
+                                 (string= (current-message) "Quit")))
+      (message msg))))
 
 (defalias #'js2-echo-help #'js2-echo-error)
 
 (defun js2-line-break (&optional soft)
-  "Break line at point."
+  "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 (syntax-ppss)))
     (cond
      ;; Check if we're inside a string.
      ((nth 3 parse-status)
-      (js2-mode-split-string parse-status))
+      (if js2-concat-multiline-strings
+          (js2-mode-split-string parse-status)
+        (insert "\n")))
      ;; Check if inside a block comment.
      ((nth 4 parse-status)
-      (js2-mode-extend-comment))
+      (js2-mode-extend-comment (nth 8 parse-status)))
      (t
-      (newline)))))
+      (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))
-         (indent (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)))))
-    (insert quote-char "\n")
-    (indent-to indent)
-    (insert "+ " quote-string)
+         (at-eol (eq js2-concat-multiline-strings 'eol)))
+    (insert quote-char)
+    (if at-eol
+        (insert " +\n")
+      (insert "\n"))
+    (unless at-eol
+      (insert "+ "))
+    (js2-indent-line)
+    (insert quote-char)
     (when (eolp)
-      (insert quote-string)
+      (insert quote-char)
       (backward-char 1))))
 
-(defun js2-mode-extend-comment ()
-  "When inside a comment block, add comment prefix."
+(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
@@ -10486,6 +10684,7 @@
                 (save-excursion
                   (skip-chars-forward " \t\r\n")
                   (not (eq (char-after) ?*))))))
+    (delete-horizontal-space)
     (insert "\n")
     (cond
      (star
@@ -10496,15 +10695,17 @@
             (insert "\n")
             (indent-to col)
             (insert "*/"))))
-     (single
-      (when (save-excursion
+     ((and single
+           (save-excursion
               (and (zerop (forward-line 1))
-                   (looking-at "\\s-*//")))
-        (indent-to col)
-        (insert "// "))))))
+                   (looking-at "\\s-*//"))))
+      (indent-to col)
+      (insert "// ")))
+    ;; Don't need to extend the comment after all.
+    (js2-indent-line)))
 
 (defun js2-beginning-of-line ()
-  "Toggle point between bol and first non-whitespace char in line.
+  "Toggles point between bol and first non-whitespace char in line.
 Also moves past comment delimiters when inside comments."
   (interactive)
   (let (node beg)
@@ -10524,7 +10725,7 @@
       (goto-char (point-at-bol))))))
 
 (defun js2-end-of-line ()
-  "Toggle point between eol and last non-whitespace char in line."
+  "Toggles point between eol and last non-whitespace char in line."
   (interactive)
   (if (eolp)
       (skip-chars-backward " \t")
@@ -10533,10 +10734,9 @@
 (defsubst js2-mode-inside-string ()
   "Return non-nil if inside a string.
 Actually returns the quote character that begins the string."
-  (let ((parse-state (syntax-ppss)))
-    (nth 3 parse-state)))
+  (nth 3 (syntax-ppss)))
 
-(defsubst js2-mode-inside-comment-or-string ()
+(defun js2-mode-inside-comment-or-string ()
   "Return non-nil if inside a comment or string."
   (or
    (let ((comment-start
@@ -10550,78 +10750,6 @@
      (or (nth 3 parse-state)
          (nth 4 parse-state)))))
 
-(defsubst js2-make-magic-delimiter (delim &optional pos)
-  "Add `js2-magic' and `js2-magic-paren' 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)
-              'face 'js2-magic-paren))
-
-(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 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.
@@ -10644,97 +10772,6 @@
       (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)
-          (with-silent-modifications
-            (put-text-property beg (or end (1+ beg))
-                               'face 'js2-magic-paren))))))
-
-(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
-             (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 (parse-partial-sexp (point-min) (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 (parse-partial-sexp (point-min) 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.
@@ -10942,7 +10979,7 @@
           (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.
@@ -10955,8 +10992,8 @@
       (while (and (zerop (forward-line direction))
                   (looking-at js2-mode-//-comment-re)
                   (eq indent (length (match-string 1))))
-        (setq pos (point-at-eol)))
-      pos)))
+        (setq pos (point-at-eol))
+      pos))))
 
 (defun js2-mode-hide-//-comments ()
   "Fold adjacent 1-line comments, showing only snippet of first one."
@@ -11020,7 +11057,7 @@
   (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"
@@ -11037,9 +11074,11 @@
 move backward across N balanced expressions."
   (interactive "p")
   (setq arg (or arg 1))
-  (if js2-mode-buffer-dirty-p
-      (js2-mode-wait-for-parse #'js2-mode-forward-sexp))
-  (let (node end (start (point)))
+  (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:
@@ -11050,19 +11089,105 @@
       (dotimes (i (- arg))
         (js2-backward-sws)
         (forward-char -1)  ; enter the node we backed up to
-        (setq node (js2-node-at-point (point) t))
-        (goto-char (if node
-                       (js2-node-abs-pos node)
-                     (point-min)))))
-    (t
-     ;; forward-sexp
-     (js2-forward-sws)
-     (dotimes (i arg)
-       (js2-forward-sws)
-       (setq node (js2-node-at-point (point) t)
-             end (if node (+ (js2-node-abs-pos node)
-                             (js2-node-len node))))
-       (goto-char (or end (point-max))))))))
+        (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))
+               (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))))))))
+
+(defun js2-mode-forward-sexp-parens (node abs-pos)
+  (cond
+   ((or (js2-array-node-p node)
+        (js2-object-node-p node)
+        (js2-array-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)
+    (let ((lp (js2-node-lp node))
+          (rp (js2-node-rp node)))
+      (cons (when lp (+ abs-pos lp))
+            (when rp (+ abs-pos rp)))))))
+
+(defun js2-node-closest-child (parent point limit &optional before)
+  (let* ((parent-pos (js2-node-abs-pos parent))
+         (rpoint (- point parent-pos))
+         (rlimit (- limit parent-pos))
+         (min (min rpoint rlimit))
+         (max (max rpoint rlimit))
+         found)
+    (catch 'done
+      (js2-visit-ast
+       parent
+       (lambda (node end-p)
+         (if (eq node parent)
+             t
+           (let ((pos (js2-node-pos node)) ;; Both relative values.
+                 (end (+ (js2-node-pos node) (js2-node-len node))))
+             (when (and (>= pos min) (<= end max)
+                        (if before (< pos rpoint) (> end rpoint)))
+               (setq found node))
+             (when (> end rpoint)
+               (throw 'done nil)))
+           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.
@@ -11070,15 +11195,11 @@
 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))
@@ -11086,13 +11207,12 @@
            (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))
@@ -11101,9 +11221,9 @@
         (setq errs (cdr errs)))
       (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)))))))
 
@@ -11166,29 +11286,45 @@
     (when (setq sib (js2-node-find-child-before (point) parent))
       (goto-char (js2-node-abs-pos sib)))))
 
-(defun js2-beginning-of-defun ()
-  "Go to line on which current function starts, and return non-nil.
-If we're not in a function, go to beginning of previous script-level element."
-  (interactive)
-  (let ((parent (js2-node-parent-script-or-fn (js2-node-at-point)))
-        pos sib)
-    (cond
-     ((and (js2-function-node-p parent)
-           (not (eq (point) (setq pos (js2-node-abs-pos parent)))))
-      (goto-char pos))
-     (t
-      (js2-mode-backward-sibling)))))
+(defun js2-beginning-of-defun (&optional arg)
+  "Go to line on which current function starts, and return t on success.
+If we're not in a function or already at the beginning of one, go
+to beginning of previous script-level element.
+With ARG N, do that N times. If N is negative, move forward."
+  (setq arg (or arg 1))
+  (if (plusp arg)
+      (let ((parent (js2-node-parent-script-or-fn (js2-node-at-point))))
+        (when (cond
+               ((js2-function-node-p parent)
+                (goto-char (js2-node-abs-pos parent)))
+               (t
+                (js2-mode-backward-sibling)))
+          (if (> arg 1)
+              (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))))))
 
 (defun js2-end-of-defun ()
-  "Go to the char after the last position of the current function.
-If we're not in a function, skips over the next script-level element."
-  (interactive)
-  (let ((parent (js2-node-parent-script-or-fn (js2-node-at-point))))
-    (if (not (js2-function-node-p parent))
-        ;; punt:  skip over next script-level element beyond point
-        (js2-mode-forward-sibling)
-      (goto-char (+ 1 (+ (js2-node-abs-pos parent)
-                         (js2-node-len parent)))))))
+  "Go to the char after the last position of the current function
+or script-level element."
+  (let* ((node (js2-node-at-point))
+         (parent (or (and (js2-function-node-p node) node)
+                     (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)
+            parent (js2-node-find-child-before (point) script))
+      (when (or (null parent)
+                (>= (point) (+ (js2-node-abs-pos parent)
+                               (js2-node-len parent))))
+        (setq parent (js2-node-find-child-after (point) script))))
+    (when parent
+      (goto-char (+ (js2-node-abs-pos parent)
+                    (js2-node-len parent))))))
 
 (defun js2-mark-defun (&optional allow-extend)
   "Put mark at end of this function, point at beginning.
@@ -11249,5 +11385,4 @@
 
 (provide 'js2-mode)
 
-;; arch-tag: 1dd56864-a3ef-4682-a63f-6341f6a42738
 ;;; js2-mode.el ends here


reply via email to

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