]> code.delx.au - gnu-emacs-elpa/blobdiff - js2-mode.el
Use \\' instead of $
[gnu-emacs-elpa] / js2-mode.el
index 5d9354de0c50740bc809c7c52b5dcfe4a8d3d120..1aa1d7200885846ca18c3c5b547c4d0fd90e6c79 100644 (file)
@@ -1,12 +1,15 @@
 ;;; js2-mode.el --- Improved JavaScript editing mode
 
-;; Copyright (C) 2009, 2012  Free Software Foundation, Inc.
+;; Copyright (C) 2009, 2011-2013  Free Software Foundation, Inc.
 
-;; Author:  Steve Yegge <steve.yegge@gmail.com>
-;; Contributors:  mooz <stillpedant@gmail.com>
-;;                Dmitry Gutov <dgutov@yandex.ru>
-;; Version:  See `js2-mode-version'
-;; Keywords:  languages, javascript
+;; Author: Steve Yegge <steve.yegge@gmail.com>
+;;         mooz <stillpedant@gmail.com>
+;;         Dmitry Gutov <dgutov@yandex.ru>
+;; URL:  https://github.com/mooz/js2-mode/
+;;       http://code.google.com/p/js2-mode/
+;; Version: 20130219
+;; Keywords: languages, javascript
+;; Package-Requires: ((emacs "24.1"))
 
 ;; This file is part of GNU Emacs.
 
@@ -28,7 +31,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
 ;;    - 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))
+;;   (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:
 ;; This means that `js2-mode' is currently only useful for editing JavaScript
 ;; files, and not for editing JavaScript within <script> tags or templates.
 
+;; The project page on GitHub is used for development and issue tracking.
+;; The original homepage at Google Code is mentioned here for posterity, it has
+;; outdated information and is mostly unmaintained.
+
 ;;; Code:
 
 (eval-when-compile
@@ -83,7 +89,6 @@
 
 (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)
@@ -92,7 +97,7 @@
 
 (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
@@ -175,13 +180,12 @@ variable `js2-include-browser-externs'.")
   "Mozilla Rhino externs.
 Set `js2-include-rhino-externs' to t to include them.")
 
-(defvar js2-gears-externs
+(defvar js2-node-externs
   (mapcar 'symbol-name
-          '(
-            ;; TODO(stevey):  add these
-            ))
-  "Google Gears externs.
-Set `js2-include-gears-externs' to t to include them.")
+          '(__dirname __filename Buffer clearInterval clearTimeout require
+            console exports global module process setInterval setTimeout))
+  "Node.js externs.
+Set `js2-include-node-externs' to t to include them.")
 
 ;;; Variables
 
@@ -220,7 +224,6 @@ Similar to `c-basic-offset'."
   :type 'integer)
 (js2-mark-safe-local 'js2-basic-offset 'integerp)
 
-
 (defcustom js2-bounce-indent-p nil
   "Non-nil to have indent-line function choose among alternatives.
 If nil, the indent-line function will indent to a predetermined column
@@ -233,39 +236,24 @@ js2-mode also binds `js2-bounce-indent-backwards' to Shift-Tab."
   :type 'boolean
   :group 'js2-mode)
 
-(defcustom js2-consistent-level-indent-inner-bracket-p t
-  "Non-nil to make indentation level inner bracket consistent,
-regardless of the beginning bracket position."
-  :group 'js2-mode
-  :type 'boolean)
-(js2-mark-safe-local 'js2-consistent-level-indent-inner-bracket-p 'booleanp)
+(defcustom js2-pretty-multiline-declarations t
+  "Non-nil to line up multiline declarations vertically:
 
-(defcustom js2-pretty-multiline-decl-indentation-p t
-  "Non-nil to line up multiline declarations vertically. See the
-function `js2-multiline-decl-indentation' for details."
-  :group 'js2-mode
-  :type 'boolean)
-(js2-mark-safe-local 'js2-pretty-multiline-decl-indentation-p 'booleanp)
+  var a = 10,
+      b = 20,
+      c = 30;
 
-(defcustom js2-always-indent-assigned-expr-in-decls-p nil
-  "If both `js2-pretty-multiline-decl-indentation-p' and this are non-nil,
-always additionally indent function expression or array/object literal
-assigned in a declaration, even when only one var is declared."
-  :group 'js2-mode
-  :type 'boolean)
-(js2-mark-safe-local 'js2-always-indent-assigned-expr-in-decls-p 'booleanp)
-
-(defcustom js2-indent-on-enter-key nil
-  "Non-nil to have Enter/Return key indent the line.
-This is unusual for Emacs modes but common in IDEs like Eclipse."
-  :type 'boolean
-  :group 'js2-mode)
+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:
 
-(defcustom js2-enter-indents-newline nil
-  "Non-nil to have Enter/Return key indent the newly-inserted line.
-This is unusual for Emacs modes but common in IDEs like Eclipse."
-  :type 'boolean
-  :group 'js2-mode)
+  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.
@@ -287,15 +275,11 @@ If `js2-dynamic-idle-timer-adjust' is 0 or negative,
   :type 'number
   :group 'js2-mode)
 
-(defcustom js2-mode-escape-quotes t
-  "Non-nil to disable automatic quote-escaping inside strings."
-  :type 'boolean
-  :group 'js2-mode)
-
-(defcustom js2-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
@@ -312,8 +296,7 @@ even if this flag is non-nil."
 
 (defcustom js2-strict-trailing-comma-warning t
   "Non-nil to warn about trailing commas in array literals.
-Ecma-262 forbids them, but many browsers permit them.  IE is the
-big exception, and can produce bugs if you have trailing commas."
+Ecma-262-5.1 allows them, but older versions of IE raise an error."
   :type 'boolean
   :group 'js2-mode)
 
@@ -349,14 +332,6 @@ parenthesizing the expression, e.g. if ((a = b)) ..."
   :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
@@ -373,12 +348,12 @@ Useful for viewing Mozilla JavaScript source code."
   :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)
 
@@ -389,7 +364,7 @@ Examples:
   var foo = {int: 5, while: 6, continue: 7};
   foo.return = 8;
 
-Ecma-262 forbids this syntax, but many browsers support it."
+Ecma-262 5.1 allows this syntax, but some engines still don't."
   :type 'boolean
   :group 'js2-mode)
 
@@ -456,9 +431,6 @@ which doesn't seem particularly useful, but Rhino permits it."
   :type 'boolean
   :group 'js2-mode)
 
-(defvar js2-mode-version 20120416
-  "Release number for `js2-mode'.")
-
 ;; scanner variables
 
 (defmacro js2-deflocal (name value &optional comment)
@@ -661,8 +633,9 @@ which doesn't seem particularly useful, but Rhino permits it."
 
 (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)
 
@@ -803,16 +776,8 @@ Will only be used when we finish implementing the interpreter.")
 (js2-deflocal js2-recorded-identifiers nil
   "Tracks identifiers found during parsing.")
 
-(defmacro js2-in-lhs (body)
-  `(let ((js2-is-in-lhs t))
-     ,body))
-
-(defmacro js2-in-rhs (body)
-  `(let ((js2-is-in-lhs nil))
-     ,body))
-
-(js2-deflocal js2-is-in-lhs nil
-  "True while parsing lhs statement")
+(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.
@@ -833,9 +798,9 @@ These are currently only used for highlighting undeclared variables,
 which only worries about top-level (unqualified) references.
 As js2-mode's processing improves, we will flesh out this list.
 
-The initial value is set to `js2-ecma-262-externs', unless you
-have set `js2-include-browser-externs', in which case the browser
-externs are also included.
+The initial value is set to `js2-ecma-262-externs', unless some
+of the `js2-include-?-externs' variables are set to t, in which
+case the browser, Rhino and/or Node.js externs are also included.
 
 See `js2-additional-externs' for more information.")
 
@@ -843,21 +808,18 @@ See `js2-additional-externs' for more information.")
   "Non-nil to include browser externs in the master externs list.
 If you work on JavaScript files that are not intended for browsers,
 such as Mozilla Rhino server-side JavaScript, set this to nil.
-You can always include them on a per-file basis by calling
-`js2-add-browser-externs' from a function on `js2-mode-hook'.
-
 See `js2-additional-externs' for more information about externs."
   :type 'boolean
   :group 'js2-mode)
 
-(defcustom js2-include-rhino-externs t
+(defcustom js2-include-rhino-externs nil
   "Non-nil to include Mozilla Rhino externs in the master externs list.
 See `js2-additional-externs' for more information about externs."
   :type 'boolean
   :group 'js2-mode)
 
-(defcustom js2-include-gears-externs t
-  "Non-nil to include Google Gears externs in the master externs list.
+(defcustom js2-include-node-externs nil
+  "Non-nil to include Node.js externs in the master externs list.
 See `js2-additional-externs' for more information about externs."
   :type 'boolean
   :group 'js2-mode)
@@ -881,7 +843,7 @@ externs appropriate for the specific file, perhaps based on its path.
 These should go in `js2-additional-externs', which is buffer-local.
 
 Finally, you can add a function to `js2-post-parse-callbacks',
-which is called after parsing completes, and `root' is bound to
+which is called after parsing completes, and `js2-mode-ast' is bound to
 the root of the parse tree.  At this stage you can set up an AST
 node visitor using `js2-visit-ast' and examine the parse tree
 for specific import patterns that may imply the existence of
@@ -1108,6 +1070,9 @@ Not currently used."
   "Face used to highlight brackets in jsdoc html tags."
   :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.
@@ -1116,17 +1081,12 @@ declarations to `js2-recorded-identifiers', which see."
   :type 'list
   :group 'js2-mode)
 
-(defface js2-external-variable
-  '((t :foreground "orange"))
-  "Face used to highlight undeclared variable identifiers.
+(defcustom js2-highlight-external-variables t
+  "Non-nil to highlight undeclared variable identifiers.
 An undeclared variable is any variable not declared with var or let
 in the current scope or any lexically enclosing scope.  If you use
 such a variable, then you are either expecting it to originate from
 another file, or you've got a potential bug."
-  :group 'js2-mode)
-
-(defcustom js2-highlight-external-variables t
-  "Non-nil to highlight undeclared variable identifiers."
   :type 'boolean
   :group 'js2-mode)
 
@@ -1139,6 +1099,7 @@ another file, or you've got a potential bug."
   (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)
@@ -1293,7 +1254,7 @@ First match-group is the leading whitespace.")
 
 ;; 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)
@@ -1522,6 +1483,12 @@ the correct number of ARGS must be provided."
 (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")
 
@@ -1586,7 +1553,7 @@ the correct number of ARGS must be provided."
          "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")
@@ -1697,7 +1664,7 @@ the correct number of ARGS must be provided."
          "Code has no side effects")
 
 (js2-msg "msg.extra.trailing.comma"
-         "Trailing comma is not legal in an ECMA-262 object initializer")
+         "Trailing comma is not supported in some browsers")
 
 (js2-msg "msg.array.trailing.comma"
          "Trailing comma yields different behavior across browsers")
@@ -1725,6 +1692,9 @@ the correct number of ARGS must be provided."
 (js2-msg "msg.assn.create.strict"
          "Assignment to undeclared variable %s")
 
+(js2-msg "msg.undeclared.variable"  ; added by js2-mode
+         "Undeclared variable or function '%s'")
+
 (js2-msg "msg.ref.undefined.prop"
          "Reference to undefined property '%s'")
 
@@ -2016,12 +1986,13 @@ Returns nil if element is not found in the list."
                   (current-column))
                 js2-ts-hit-eof))))
 
-(defun 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)))
 
 (defun js2-add-strict-warning (msg-id &optional msg-arg beg end)
@@ -2484,14 +2455,15 @@ NAME can be a Lisp symbol or string.  SYMBOL is a `js2-symbol'."
                                                      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)
@@ -2503,13 +2475,16 @@ NAME can be a Lisp symbol or string.  SYMBOL is a `js2-symbol'."
 
 (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))
@@ -2922,7 +2897,8 @@ a `js2-label-node' or the innermost enclosing loop.")
                                                        (ftype 'FUNCTION)
                                                        (form 'FUNCTION_STATEMENT)
                                                        (name "")
-                                                       params body
+                                                       params rest-p
+                                                       body
                                                        lp rp)))
   "AST node for a function declaration.
 The `params' field is a Lisp list of nodes.  Each node is either a simple
@@ -2932,6 +2908,7 @@ The `params' field is a Lisp list of nodes.  Each node is either a simple
   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
+  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
@@ -2954,6 +2931,7 @@ The `params' field is a Lisp list of nodes.  Each node is either a simple
         (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
@@ -2966,9 +2944,11 @@ The `params' field is a Lisp list of nodes.  Each node is either a simple
           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"))
@@ -3464,7 +3444,7 @@ The `right' field is a `js2-node' representing the initializer value.")
 (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
@@ -3661,6 +3641,7 @@ as opposed to required parens such as those enclosing an if-conditional."
                                                               object in-pos
                                                               foreach-p
                                                               each-pos
+                                                              forof-p
                                                               lp rp)))
   "AST subtree for each 'for (foo in bar)' loop in an array comprehension.")
 
@@ -3676,7 +3657,9 @@ as opposed to required parens such as those enclosing an if-conditional."
   (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 ")"))
 
@@ -4226,7 +4209,7 @@ 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)
@@ -5028,8 +5011,9 @@ nor always false."
           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.")
@@ -5055,7 +5039,7 @@ Signals an error if it's not a recognized token."
 (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
@@ -5643,7 +5627,9 @@ corresponding number.  Otherwise return -1."
              (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))))
@@ -6172,6 +6158,8 @@ Shown at or above `js2-highlight-level' 2.")
       "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__"
@@ -6224,7 +6212,7 @@ Shown at or above `js2-highlight-level' 3.")
         (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.
@@ -6243,7 +6231,7 @@ The variable `js2-highlight-level' governs this highighting."
         (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
@@ -6492,9 +6480,8 @@ it is considered declared."
                     (member name js2-default-externs)
                     (member name js2-additional-externs)
                     (js2-get-defining-scope scope name))
-          (js2-set-face pos end 'js2-external-variable 'record)
-          (js2-record-text-property pos end 'help-echo "Undeclared variable")
-          (js2-record-text-property pos end 'point-entered #'js2-echo-help))))
+          (js2-report-warning "msg.undeclared.variable" name pos (- end pos)
+                              'js2-external-variable))))
     (setq js2-recorded-identifiers nil)))
 
 ;;; IMenu support
@@ -6612,7 +6599,8 @@ the parent function, look up its qname, then prepend a copy of it to the chain."
 (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)
@@ -6705,7 +6693,7 @@ that it's an external variable, which must also be in the top-level scope."
      (t t))))
 
 (defun js2-wrapper-function-p (node)
-  "Returns t if NODE is a function expression that's immediately invoked.
+  "Return t if NODE is a function expression that's immediately invoked.
 NODE must be `js2-function-node'."
   (let ((parent (js2-node-parent node)))
     (or
@@ -6728,9 +6716,9 @@ For instance, processing a nested scope requires a parent function node."
     (dolist (entry entries)
       ;; function node goes first
       (destructuring-bind (current-fn &rest (&whole chain head &rest)) entry
-        ;; examine its defining scope;
-        ;; if top-level/external, keep as-is
-        (if (js2-node-top-level-decl-p head)
+        ;; 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
@@ -6888,8 +6876,8 @@ i.e. one or more nodes, and an integer position as the list tail."
 
 ;;; 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."
@@ -6964,7 +6952,7 @@ The flags, if any, are saved in `js2-current-flagged-token'."
       tt)))  ; return unflagged token
 
 (defun js2-peek-flagged-token ()
-  "Returns the current token along with any flags set for it."
+  "Return the current token along with any flags set for it."
   (js2-peek-token)
   js2-current-flagged-token)
 
@@ -6989,11 +6977,23 @@ Returns nil and consumes nothing if MATCH is not the next token."
     (js2-consume-token)
     t))
 
+(defun js2-match-contextual-kwd (name)
+  "Consume and return t if next token is `js2-NAME', and its
+string is NAME.  Returns nil and does nothing otherwise."
+  (if (or (/= (js2-peek-token) js2-NAME)
+          (not (string= js2-ts-string name)))
+      nil
+    (js2-consume-token)
+    (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)))
 
 (defun js2-match-prop-name ()
   "Consume token and return t if next token is a valid property name.
@@ -7187,10 +7187,12 @@ Scanner should be initialized."
         (push comment (js2-ast-root-comments root))
         (js2-node-add-children root comment)))
     (setf (js2-node-len root) (- end pos))
+    (setq js2-mode-ast root)  ; Make sure this is available for callbacks.
     ;; Give extensions a chance to muck with things before highlighting starts.
     (let ((js2-additional-externs js2-additional-externs))
-      (dolist (callback js2-post-parse-callbacks)
-        (funcall callback))
+      (save-excursion
+        (dolist (callback js2-post-parse-callbacks)
+          (funcall callback)))
       (js2-highlight-undeclared-vars))
     root))
 
@@ -7266,28 +7268,60 @@ NODE is either `js2-array-node', `js2-object-node', or `js2-name-node'."
 (defun js2-parse-function-params (fn-node pos)
   (if (js2-match-token js2-RP)
       (setf (js2-function-node-rp fn-node) (- js2-token-beg pos))
-    (let (params len param)
+    (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))
-              (setq param (js2-parse-primary-expr-lhs))
+              (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))
-             ;; simple name
+             ;; 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))))))
@@ -7691,7 +7725,8 @@ Return value is a list (EXPR LP RP), with absolute paren positions."
   "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
@@ -7721,8 +7756,11 @@ Parses for, for-in, and for each-in statements."
             (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
+      (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.
@@ -7740,7 +7778,7 @@ Parses for, for-in, and for each-in statements."
                      (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)
+      (if (not is-for-in-or-of)
           (setq pn (make-js2-for-node :init init
                                       :condition cond
                                       :update incr
@@ -7759,6 +7797,7 @@ Parses for, for-in, and for each-in statements."
                                        :in-pos in-pos
                                        :foreach-p is-for-each
                                        :each-pos each-pos
+                                       :forof-p is-for-of
                                        :lp lp
                                        :rp rp)))
       (unwind-protect
@@ -7820,9 +7859,8 @@ Parses for, for-in, and for each-in statements."
            ;; destructuring pattern
            ;;     catch ({ message, file }) { ... }
            ((or (= tt js2-LB) (= tt js2-LC))
-            (setq param
-                  (js2-define-destruct-symbols (js2-parse-primary-expr-lhs)
-                                               js2-LET nil)))
+            (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")
@@ -8249,7 +8287,7 @@ Returns the parsed `js2-var-decl-node' expression node."
             init nil)
       (if (or (= tt js2-LB) (= tt js2-LC))
           ;; Destructuring assignment, e.g., var [a, b] = ...
-          (setq destructuring (js2-parse-primary-expr-lhs)
+          (setq destructuring (js2-parse-destruct-primary-expr)
                 end (js2-node-end destructuring))
         ;; Simple variable name
         (when (js2-must-match js2-NAME "msg.bad.var")
@@ -8261,10 +8299,7 @@ Returns the parsed `js2-var-decl-node' expression node."
       (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
@@ -8422,9 +8457,7 @@ If NODE is non-nil, it is the AST node associated with the symbol."
                                        :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)))
@@ -9015,8 +9048,8 @@ For instance, @[expr], @*::[expr], or ns::[expr]."
                                           :rb (js2-relpos rb pos)))
       (js2-node-add-children pn namespace expr))))
 
-(defun js2-parse-primary-expr-lhs ()
-  (let ((js2-is-in-lhs t))
+(defun js2-parse-destruct-primary-expr ()
+  (let ((js2-is-in-destructuring t))
     (js2-parse-primary-expr)))
 
 (defun js2-parse-primary-expr ()
@@ -9139,7 +9172,7 @@ array-literals, array comprehensions and regular expressions."
         (after-lb-or-comma t)
         after-comma tt elems pn
         (continue t))
-    (unless js2-is-in-lhs
+    (unless js2-is-in-destructuring
         (js2-push-scope (make-js2-scope))) ; for array comp
     (while continue
       (setq tt (js2-peek-token))
@@ -9163,16 +9196,16 @@ array-literals, array comprehensions and regular expressions."
                                       :len (- js2-ts-cursor pos)
                                       :elems (nreverse elems)))
         (apply #'js2-node-add-children pn (js2-array-node-elems pn))
-        (when (and after-comma (not js2-is-in-lhs))
+        (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-lhs
+       (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-primary-expr-lhs)
+                  (js2-parse-destruct-primary-expr)
                 ;; invalid pattern
                 (js2-consume-token)
                 (js2-report-error "msg.bad.var")
@@ -9196,7 +9229,7 @@ array-literals, array comprehensions and regular expressions."
         (push (js2-parse-assign-expr) elems)
         (setq after-lb-or-comma nil
               after-comma nil))))
-    (unless js2-is-in-lhs
+    (unless js2-is-in-destructuring
       (js2-pop-scope))
     pn))
 
@@ -9235,11 +9268,11 @@ We should have just parsed the 'for' keyword before calling this function."
     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
@@ -9257,12 +9290,10 @@ Last token peeked should be the initial FOR."
           (cond
            ((or (= tt js2-LB)
                 (= tt js2-LC))
-            ;; handle destructuring assignment
-            (setq iter (js2-parse-primary-expr-lhs))
+            (setq iter (js2-parse-destruct-primary-expr))
             (js2-define-destruct-symbols iter js2-LET
                                          'font-lock-variable-name-face t))
-           ((js2-valid-prop-name-token tt)
-            (js2-consume-token)
+           ((js2-match-token js2-NAME)
             (setq iter (js2-create-name-node)))
            (t
             (js2-report-error "msg.bad.var")))
@@ -9270,8 +9301,12 @@ Last token peeked should be the initial FOR."
           ;; 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)))
@@ -9282,6 +9317,7 @@ Last token peeked should be the initial FOR."
                 (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))
@@ -9332,7 +9368,7 @@ Last token peeked should be the initial FOR."
 
 (defun js2-parse-named-prop (tt)
   "Parse a name, string, or getter/setter object property.
-When `js2-is-in-lhs' is t, forms like {a, b, c} will be permitted."
+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)))
@@ -9352,12 +9388,14 @@ When `js2-is-in-lhs' is t, forms like {a, b, c} will be permitted."
       (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 bind e.g., {a, b} = c;
-     ;; XXX: To be honest, the value of `js2-is-in-lhs' becomes t only when
-     ;; patterns are appeared in variable declaration, function parameters, and catch-clause.
-     ;; We have to set t to `js2-is-in-lhs' when the current expressions are part of any
-     ;; assignment but it's difficult because it requires looking ahead of expression.
-     ((and js2-is-in-lhs
+     ;; 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)
@@ -9606,13 +9644,19 @@ and comments have been removed."
   (save-excursion
     (back-to-indentation)
     (or (js2-looking-at-operator-p)
-        (when (js2-re-search-backward "\n" nil t)  ;; skip comments
-          (skip-chars-backward " \t")
-          (unless (bolp) ;; previous line is empty
+        (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 (js2-looking-at-operator-p)
             (backward-char)
-            (when (js2-looking-at-operator-p)
-              (backward-char)
-              (not (looking-at "\\*\\|++\\|--\\|/[/*]"))))))))
+            (not (looking-at "\\*\\|++\\|--\\|/[/*]")))))))
 
 (defun js2-end-of-do-while-loop-p ()
   "Return non-nil if word after point is `while' of a do-while
@@ -9638,22 +9682,8 @@ indented to the same column as the current line."
                    (= (current-indentation) saved-indent))))))))
 
 (defun js2-multiline-decl-indentation ()
-  "Returns the declaration indentation column if the current line belongs
-to a multiline declaration statement.  All declarations are lined up vertically:
-
-var a = 10,
-    b = 20,
-    c = 30;
-
-Note that if `js2-always-indent-assigned-expr-in-decls-p' is nil, and the first
-assigned expression is a function or array/object literal, it will be indented
-differently:
-
-var o = {                               var bar = 2,
-  foo: 3                                    o = {
-},                                            foo: 3
-    bar = 2;                                };
-"
+  "Return the declaration indentation column if the current line belongs
+to a multiline declaration statement.  See `js2-pretty-multiline-declarations'."
   (let (forward-sexp-function ; use Lisp version
         at-opening-bracket)
     (save-excursion
@@ -9678,7 +9708,8 @@ var o = {                               var bar = 2,
               (backward-sexp)
             (scan-error (setq at-opening-bracket t))))
         (when (looking-at js2-declaration-keyword-re)
-          (- (1+ (match-end 0)) (point-at-bol)))))))
+          (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.
@@ -9726,8 +9757,9 @@ In particular, return the buffer position of the first `for' kwd."
             ;; so we'll just guess at it.
             (if (and (> end (point)) ; not empty literal
                      (re-search-forward "[^,]]* \\(for\\) " end t)
-                     ;; not inside a string literal
-                     (not (nth 3 (parse-partial-sexp bracket (point)))))
+                     ;; 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)
@@ -9749,13 +9781,14 @@ In particular, return the buffer position of the first `for' kwd."
     (let ((ctrl-stmt-indent (js2-ctrl-statement-indentation))
           (same-indent-p (looking-at "[]})]\\|\\<case\\>\\|\\<default\\>"))
           (continued-expr-p (js2-continued-expression-p))
-          (declaration-indent (and js2-pretty-multiline-decl-indentation-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
@@ -9774,25 +9807,19 @@ In particular, return the buffer position of the first `for' kwd."
         (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 (and (nth 1 p)
-                     (not js2-consistent-level-indent-inner-bracket-p))
-                (progn (goto-char (1+ (nth 1 p)))
-                       (skip-chars-forward " \t"))
-              (back-to-indentation)
-              (when (and js2-pretty-multiline-decl-indentation-p
-                         js2-always-indent-assigned-expr-in-decls-p
-                         (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)))))
+          (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)
@@ -10073,10 +10100,10 @@ If so, we don't ever want to use bounce-indent."
         ;; 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)))
+                         (syntax-ppss (point-at-bol)))
           offset (- (point) (save-excursion
-                               (back-to-indentation)
-                               (point))))
+                              (back-to-indentation)
+                              (point))))
     (js2-with-underscore-as-word-syntax
      (if (nth 4 parse-status)
          (js2-lineup-comment parse-status)
@@ -10090,9 +10117,9 @@ If so, we don't ever want to use bounce-indent."
               (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))))))
+        (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."
@@ -10110,6 +10137,7 @@ If so, we don't ever want to use bounce-indent."
     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,
@@ -10129,16 +10157,16 @@ highlighting features of `js2-mode'."
   (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)))
+                (if js2-include-rhino-externs js2-rhino-externs)
+                (if js2-include-node-externs js2-node-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
-        js2-highlight-level 0)  ; no syntax highlighting
+        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))
@@ -10154,22 +10182,114 @@ highlighting features of `js2-mode'."
   (js2-remove-overlays)
   (setq js2-mode-ast nil))
 
-(defun js2-display-error-list ()
+(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)
-  (if (not (js2-have-errors-p))
-      (message "No errors")
-    (let ((srcbuf (current-buffer))
-          (errbuf (get-buffer-create "*js-lint*"))
-          (errs (js2-errors-and-warnings)))
-      (setq errs (sort errs (lambda (e1 e2)
-                              (funcall '< (second e1) (second e2)))))
+  (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 errs)
-            (insert (format "%s\n" err)))
-          (pop-to-buffer errbuf))))))
+          (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"
@@ -10222,8 +10342,8 @@ highlighting features of `js2-mode'."
   (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)))
+                (if js2-include-rhino-externs js2-rhino-externs)
+                (if js2-include-node-externs js2-node-externs)))
 
   (setq font-lock-defaults '(nil t))
 
@@ -10236,15 +10356,12 @@ highlighting features of `js2-mode'."
   (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)
 
-  (if (fboundp 'run-mode-hooks)
-      (run-mode-hooks 'js2-mode-hook)
-    (run-hooks 'js2-mode-hook))
-
   (setq js2-mode-functions-hidden nil
         js2-mode-comments-hidden nil
         js2-mode-buffer-dirty-p t
@@ -10270,9 +10387,12 @@ highlighting features of `js2-mode'."
   (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-mode-idle-reparse (current-buffer))))
+  (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
@@ -10329,7 +10449,7 @@ buffer will only rebuild its `js2-mode-ast' if the buffer is dirty."
                     (js2-time
                      (setq interrupted-p
                            (catch 'interrupted
-                             (setq js2-mode-ast (js2-parse))
+                             (js2-parse)
                              ;; if parsing is interrupted, comments and regex
                              ;; literals stay ignored by `parse-partial-sexp'
                              (remove-text-properties (point-min) (point-max)
@@ -10352,12 +10472,14 @@ buffer will only rebuild its `js2-mode-ast' if the buffer is dirty."
         (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)
@@ -10397,7 +10519,8 @@ P1 and P2 are the old and new values of point, respectively."
 
 (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)))
@@ -10406,7 +10529,7 @@ E is a list of ((MSG-KEY MSG-ARG) BEG END)."
          (end (max (point-min) (min end (point-max))))
          (js2-highlight-level 3)    ; so js2-set-face is sure to fire
          (ovl (make-overlay beg end)))
-    (overlay-put ovl 'font-lock-face face)
+    (overlay-put ovl 'font-lock-face (or (fourth e) face))
     (overlay-put ovl 'js2-error t)
     (put-text-property beg end 'help-echo (js2-get-msg key))
     (put-text-property beg end 'point-entered #'js2-echo-error)))
@@ -10484,54 +10607,56 @@ This ensures that the counts and `next-error' are correct."
 (defun js2-echo-error (old-point new-point)
   "Called by point-motion hooks."
   (let ((msg (get-text-property new-point 'help-echo)))
-    (if (and msg (or (not (current-message))
-                     (string= (current-message) "Quit")))
-        (message msg))))
+    (when (and (stringp msg) (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 ()
+(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
@@ -10560,6 +10685,7 @@ PARSE-STATUS is as documented in `parse-partial-sexp'."
                 (save-excursion
                   (skip-chars-forward " \t\r\n")
                   (not (eq (char-after) ?*))))))
+    (delete-horizontal-space)
     (insert "\n")
     (cond
      (star
@@ -10575,13 +10701,12 @@ PARSE-STATUS is as documented in `parse-partial-sexp'."
               (and (zerop (forward-line 1))
                    (looking-at "\\s-*//"))))
       (indent-to col)
-      (insert "// "))
-     ;; don't need to extend the comment after all
-     (js2-enter-indents-newline
-      (js2-indent-line)))))
+      (insert "// ")))
+    ;; Don't need to extend the comment after all.
+    (js2-indent-line)))
 
 (defun js2-beginning-of-line ()
-  "Toggles point between bol and first non-whitespace char in line.
+  "Toggle point between bol and first non-whitespace char in line.
 Also moves past comment delimiters when inside comments."
   (interactive)
   (let (node beg)
@@ -10601,7 +10726,7 @@ Also moves past comment delimiters when inside comments."
       (goto-char (point-at-bol))))))
 
 (defun js2-end-of-line ()
-  "Toggles point between eol and last non-whitespace char in line."
+  "Toggle point between eol and last non-whitespace char in line."
   (interactive)
   (if (eolp)
       (skip-chars-backward " \t")
@@ -10950,7 +11075,11 @@ With ARG, do it that many times.  Negative arg -N means
 move backward across N balanced expressions."
   (interactive "p")
   (setq arg (or arg 1))
-  (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:
@@ -10961,19 +11090,83 @@ move backward across N balanced expressions."
       (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."
@@ -10990,10 +11183,12 @@ move backward across N balanced expressions."
   (or (js2-errors) (js2-warnings)))
 
 (defun js2-errors-and-warnings ()
-  "Return a copy of the concatenated errors and warnings lists."
-  (and js2-mode-ast
-       (append (js2-ast-root-errors js2-mode-ast)
-               (copy-sequence (js2-ast-root-warnings js2-mode-ast)))))
+  "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.