<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,7 +1,10 @@
 #$Id: Makefile,v 1.1 2008-04-14 20:08:41 stevey Exp $
 
-EMACS=emacs
+EMACS22=emacs22
+EMACS23=emacs-snapshot
 BUILD=-batch -q -l js2-build.el -f js2-build-js2-mode
 
 all:
-	$(EMACS) $(BUILD)
+	$(EMACS22) $(BUILD)
+	$(EMACS23) $(BUILD)
+</diff>
      <filename>js2-mode/Makefile</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,10 @@
-;;; js2-build.el -- Build script for js2-mode
+;;; js2-build.el --- Build script for js2-mode
 
 ;; Author:  Steve Yegge (steve.yegge@gmail.com)
 ;; Keywords:  javascript languages
 
+;; This file is NOT part of GNU Emacs.
+
 ;; This program is free software; you can redistribute it and/or
 ;; modify it under the terms of the GNU General Public License as
 ;; published by the Free Software Foundation; either version 2 of
@@ -27,77 +29,46 @@
                       command-line-default-directory))
   &quot;Directory where js2-mode is installed. &quot;)
 
-(add-to-list 'load-path js2-mode-directory)
-
-(require 'js2-util)
-
-(defconst js2-build-js2-mode-files
-  '(&quot;js2-vars&quot;
-    &quot;js2-util&quot;
-    &quot;js2-scan&quot;
-    &quot;js2-messages&quot;
-    &quot;js2-ast&quot;
-    &quot;js2-highlight&quot;
-    &quot;js2-browse&quot;
-    &quot;js2-parse&quot;
-    &quot;js2-indent&quot;
-    &quot;js2-mode&quot;)
-  &quot;Files used to produce the `js2-mode' final build.
-The order of the files in the list is significant.&quot;)
-
 (defconst js2-build-directory
   (concat js2-mode-directory &quot;build/&quot;)
   &quot;Where `js2-build-js2-mode' build artifacts are deposited.&quot;)
   
 (defun js2-build-js2-mode ()
-  &quot;Concatenates and byte-compiles the js2-mode files.
+  &quot;Byte-compile js2-mode.el.
 Rewrites some of the code on the fly.&quot;
   (interactive)
-  (when (get-buffer &quot;js2.el&quot;)
+  (when (get-buffer &quot;js2-mode.el&quot;)
     (save-excursion
-      (set-buffer &quot;js2.el&quot;)
+      (set-buffer &quot;js2-mode.el&quot;)
       (set-buffer-modified-p nil)
-      (kill-buffer &quot;js2.el&quot;)))
+      (kill-buffer &quot;js2-mode.el&quot;)))
   (save-some-buffers)
   (unless (file-exists-p js2-build-directory)
     (make-directory js2-build-directory))
-  (let* ((root default-directory)
-         filename
-         bufname
-         buf
-         (outfile (concat js2-build-directory &quot;js2.el&quot;))
-         output)
-    (setq output (find-file-noselect outfile))
-    (save-excursion
-      (set-buffer output)
-      (setq buffer-undo-list t)
-      (erase-buffer)
-      (dolist (file (reverse js2-build-js2-mode-files))
-        (setq bufname (concat file &quot;.el&quot;)
-              buf (get-buffer bufname)
-              filename (concat root bufname))
-        (message &quot;inserting %s&quot; filename)
-        (with-buffer output
-          (insert-file-contents filename)))
-      (js2-build-fixup-output)
-      (save-buffer))
-    (copy-file outfile (concat js2-build-directory &quot;js2-&quot;
+  (let ((infile (concat js2-mode-directory &quot;js2-mode.el&quot;))
+        (outfile (concat js2-build-directory &quot;js2-mode.el&quot;)))
+    (copy-file infile outfile 'overwrite)
+    (copy-file outfile (concat js2-build-directory &quot;js2-mode-&quot;
                                (js2-build-version-number)
                                &quot;.el&quot;)
-               t) ; overwrite
+               'overwrite)
     (if (get-buffer &quot;*Compile-Log*&quot;)
         (kill-buffer &quot;*Compile-Log*&quot;))
     (byte-compile-file outfile)
-    (pop-to-buffer &quot;js2.el&quot;)
+    (let ((dest (format &quot;%sjs2-emacs%d.elc&quot; js2-build-directory
+                       emacs-major-version)))
+      (copy-file (format &quot;%sc&quot; outfile) dest 'overwrite)
+      (message &quot;Wrote %s&quot; dest))
+    (pop-to-buffer &quot;js2-mode.el&quot;)
     (toggle-read-only 1)
     (pop-to-buffer &quot;*Compile-Log*&quot;)
     (message &quot;finished compilation&quot;)))
 
 (defun js2-build-version-number ()
   (let* ((s (split-string (current-time-string)))  ; &quot;Mon Mar 24 19:09:09 2008&quot;
-         (month (second s))
-         (day (third s))
-         (year (fifth s)))
+         (month (nth 1 s))
+         (day (nth 2 s))
+         (year (nth 4 s)))
     (setq month (cdr (assoc month '((&quot;Jan&quot; . &quot;01&quot;)
                                     (&quot;Feb&quot; . &quot;02&quot;)
                                     (&quot;Mar&quot; . &quot;03&quot;)
@@ -112,77 +83,6 @@ Rewrites some of the code on the fly.&quot;
                                     (&quot;Dec&quot; . &quot;12&quot;)))))
     (format &quot;%s%s%02d&quot; year month (string-to-number day))))
 
-(defun js2-build-fixup-output ()
-  (let ((version (js2-build-version-number))  ; horks match data
-        beg end)
-    (delete-trailing-whitespace)
-    (goto-char (point-min))
-    ;; remove requires of js2 stuff
-    (while (re-search-forward &quot;^(require 'js2-.+).*\n&quot; nil t)
-      (replace-match &quot;&quot;))
-
-    ;; update version number
-    (goto-char (point-min))
-    (re-search-forward &quot;(defvar js2-mode-version \\([0-9]+\\)&quot;)
-    (replace-match version t t nil 1)
-    ;; update ELPA version number
-    (goto-char (point-min))
-    (re-search-forward &quot;^;; Version: \\(2[0-9]+\\)&quot;)
-    (replace-match version t t nil 1)
-
-    ;; move js2-mode documentation to top
-    (goto-char (point-min))
-    (re-search-forward &quot;^;;; js2-mode.el&quot;)
-    (setq beg (match-beginning 0))
-    (re-search-forward &quot;^;;; Code:\n&quot;)
-    (setq end (match-end 0))
-    (copy-region-as-kill beg end)
-    (delete-region beg end)
-    (goto-char (point-min))
-    (yank)
-
-    ;; remove remaining gpl file headers
-    (goto-char (point-min))
-    (re-search-forward &quot;^;+ Code:&quot;)  ; skip first header
-    (while (re-search-forward &quot;^;; This program is free software;&quot; nil t)
-      (setq beg (match-beginning 0))
-      (if (re-search-forward &quot;^;; MA 02111-1307 USA\n+&quot; nil t)
-          (delete-region beg (match-end 0))))
-
-    ;; update various dev-mode flags
-    (dolist (var '(&quot;js2-mode-dev-mode-p&quot;
-                   &quot;js2-allow-rhino-new-expr-initializer&quot;
-                   &quot;js2-allow-member-expr-as-function-name&quot;))
-      (goto-char (point-min))
-      (re-search-forward (concat &quot;(def\\(?:var\\|custom\\) &quot; var &quot; \\(t\\|nil\\)&quot;))
-      (replace-match &quot;nil&quot; t t nil 1))
-
-    ;; add &quot;this file auto-generated&quot; header stuff
-
-    ;; `byte-compile-dynamic' has a bug in Emacs 21
-    ;; I'm not sure if `byte-compile-dynamic' is worth it in any case:
-    ;;  - it gives users a scary risky-local-variable prompt
-    ;;  - our mode pretty much loads everything anyway
-    ;; leaving it out for now.
-    ;;
-    ;; &quot;;;; -*- eval: (set (make-local-variable 'byte-compile-dynamic)&quot;
-    ;;   &quot;(&gt; emacs-major-version 21)); -*-\n\n&quot;
-
-    (goto-char (point-min))
-    (insert &quot;;;; js2.el -- an improved JavaScript editing mode\n&quot;
-            &quot;;;;\n&quot;
-            &quot;;;; This file was auto-generated on &quot;
-            (current-time-string)
-            &quot; from files:\n;;;  &quot;
-            (mapconcat (lambda (s)
-                         (concat s &quot;.el&quot;))
-                       js2-build-js2-mode-files &quot;\n;;;  &quot;)
-            &quot;\n\n&quot;)
-
-    ;; Close the file per the coding comment guidelines.  Also for ELPA.
-    (goto-char (point-max))
-    (insert &quot;\n\n;;; js2.el ends here\n&quot;)))
-
 (provide 'js2-build)
 
 ;;; js2-build.el ends here</diff>
      <filename>js2-mode/js2-build.el</filename>
    </modified>
    <modified>
      <diff>@@ -1,101 +1,64 @@
 ;;; js2-mode.el --- an improved JavaScript editing mode
 
-;; Author:  Steve Yegge (steve.yegge@gmail.com)
-;; Version: 20080403
-;; Keywords:  javascript languages
+;; Copyright (C) 2009  Free Software Foundation, Inc.
 
-;; This program is free software; you can redistribute it and/or
-;; modify it under the terms of the GNU General Public License as
-;; published by the Free Software Foundation; either version 2 of
-;; the License, or (at your option) any later version.
+;; Author:  Steve Yegge &lt;steve.yegge@gmail.com&gt;
+;; Version:  See `js2-mode-version'
+;; Keywords:  languages, javascript
 
-;; This program is distributed in the hope that it will be
-;; useful, but WITHOUT ANY WARRANTY; without even the implied
-;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-;; PURPOSE.  See the GNU General Public License for more details.
+;; This file is part of GNU Emacs.
 
-;; You should have received a copy of the GNU General Public
-;; License along with this program; if not, write to the Free
-;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
-;; MA 02111-1307 USA
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see &lt;http://www.gnu.org/licenses/&gt;.
 
 ;;; Commentary:
 
 ;; This JavaScript editing mode supports:
-;;
-;;  - the full JavaScript language through version 1.7
-;;  - support for most Rhino and SpiderMonkey extensions from 1.5 to 1.7
+
+;;  - strict recognition of the Ecma-262 language standard
+;;  - support for most Rhino and SpiderMonkey extensions from 1.5 to 1.8
+;;  - parsing support for ECMAScript for XML (E4X, ECMA-357)
 ;;  - accurate syntax highlighting using a recursive-descent parser
-;;  - syntax-error and strict-mode warning reporting
+;;  - on-the-fly reporting of syntax errors and strict-mode warnings
+;;  - undeclared-variable warnings using a configurable externs framework
 ;;  - &quot;bouncing&quot; line indentation to choose among alternate indentation points
-;;  - smart line-wrapping within comments (Emacs 22+) and strings
+;;  - smart line-wrapping within comments and strings
 ;;  - code folding:
 ;;    - show some or all function bodies as {...}
 ;;    - show some or all block comments as /*...*/
 ;;  - context-sensitive menu bar and popup menus
 ;;  - code browsing using the `imenu' package
-;;  - typing helpers (e.g. inserting matching braces/parens)
+;;  - typing helpers such as automatic insertion of matching braces/parens
 ;;  - many customization options
-;;
-;; It is only compatible with GNU Emacs versions 21 and higher (not XEmacs).
-;;
-;; Installation:
-;;
-;;  - put `js2.el' somewhere in your emacs load path
-;;  - M-x byte-compile-file RET &lt;path-to-js2.el&gt; RET
-;;    Note:  it will refuse to run unless byte-compiled
-;;  - add these lines to your .emacs file:
-;;    (autoload 'js2-mode &quot;js2&quot; nil t)
-;;    (add-to-list 'auto-mode-alist '(&quot;\\.js$&quot; . js2-mode))
-;;
+
 ;; To customize how it works:
 ;;   M-x customize-group RET js2-mode RET
-;;
-;; The variable `js2-mode-version' is a date stamp.  When you upgrade
-;; to a newer version, you must byte-compile the file again.
-;;
+
 ;; Notes:
-;;
-;; This mode is different in many ways from standard Emacs language editing
-;; modes, inasmuch as it attempts to be more like an IDE.  If this drives
-;; you crazy, it IS possible to customize it to be more like other Emacs
-;; editing modes.  Please customize the group `js2-mode' to see all of the
-;; configuration options.
-;;
-;; Some of the functionality does not work in Emacs 21 -- upgrading to
-;; Emacs 22 or higher will get you better results.  If you byte-compiled
-;; js2.el with Emacs 21, you should re-compile it for Emacs 22.
-;;
+
+;; This mode includes a port of Mozilla Rhino's scanner, parser and
+;; symbol table.  Ideally it should stay in sync with Rhino, keeping
+;; `js2-mode' current as the EcmaScript language standard evolves.
+
 ;; Unlike cc-engine based language modes, js2-mode's line-indentation is not
 ;; customizable.  It is a surprising amount of work to support customizable
 ;; indentation.  The current compromise is that the tab key lets you cycle among
 ;; various likely indentation points, similar to the behavior of python-mode.
-;;
-;; This mode does not yet work with &quot;multi-mode&quot; modes such as mmm-mode
-;; and mumamo, although it could possibly be made to do so with some effort.
-;; This means that js2-mode is currently only useful for editing JavaScript
+
+;; This mode does not yet work with &quot;multi-mode&quot; modes such as `mmm-mode'
+;; and `mumamo', although it could be made to do so with some effort.
+;; This means that `js2-mode' is currently only useful for editing JavaScript
 ;; files, and not for editing JavaScript within &lt;script&gt; tags or templates.
-;;
-;; This code is part of a larger project, in progress, to enable writing
-;; Emacs customizations in JavaScript.
-;;
-;; Please email bug reports and suggestions to the author, or submit them
-;; at http://code.google.com/p/js2-mode/issues
-
-;; TODO:
-;;  - add unreachable-code warning (error?) using the inconsistent-return analysis
-;;  - labeled stmt length is now 1
-;;  - &quot;anonymous function does not always return a value&quot; - use getter/setter name
-;;  - extend js2-missing-semi-one-line-override to handle catch (e) {return x}
-;;  - set a text prop on autoinserted delimiters and don't biff user-entered ones
-;;  - when inserting magic curlies, look for matching close-curly before inserting
-;;  - get more use out of the symbol table:
-;;    - jump to declaration (put hyperlinks on all non-decl var usages?)
-;;    - rename variable/function
-;;    - warn on unused var
-;;  - add some dabbrev-expansions for built-in keywords like finally, function
-;;  - add at least some completion support, e.g. for built-ins
-;;  - code formatting
 
 ;;; Code:
 
@@ -105,10 +68,10091 @@
 (require 'imenu)
 (require 'cc-cmds)  ; for `c-fill-paragraph'
 
-(require 'js2-vars)
-(require 'js2-parse)
-(require 'js2-messages)
-(require 'js2-indent)
+(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.
+
+;;; 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
+          Math NaN Number Object RangeError ReferenceError RegExp
+          String SyntaxError TypeError URIError arguments
+          decodeURI decodeURIComponent encodeURI
+          encodeURIComponent escape eval isFinite isNaN
+          parseFloat parseInt undefined unescape))
+&quot;Ecma-262 externs.  Included in `js2-externs' by default.&quot;)
+
+(defvar js2-browser-externs
+  (mapcar 'symbol-name
+          '(;; DOM level 1
+            Attr CDATASection CharacterData Comment DOMException
+            DOMImplementation Document DocumentFragment
+            DocumentType Element Entity EntityReference
+            ExceptionCode NamedNodeMap Node NodeList Notation
+            ProcessingInstruction Text
+
+            ;; DOM level 2
+            HTMLAnchorElement HTMLAppletElement HTMLAreaElement
+            HTMLBRElement HTMLBaseElement HTMLBaseFontElement
+            HTMLBodyElement HTMLButtonElement HTMLCollection
+            HTMLDListElement HTMLDirectoryElement HTMLDivElement
+            HTMLDocument HTMLElement HTMLFieldSetElement
+            HTMLFontElement HTMLFormElement HTMLFrameElement
+            HTMLFrameSetElement HTMLHRElement HTMLHeadElement
+            HTMLHeadingElement HTMLHtmlElement HTMLIFrameElement
+            HTMLImageElement HTMLInputElement HTMLIsIndexElement
+            HTMLLIElement HTMLLabelElement HTMLLegendElement
+            HTMLLinkElement HTMLMapElement HTMLMenuElement
+            HTMLMetaElement HTMLModElement HTMLOListElement
+            HTMLObjectElement HTMLOptGroupElement
+            HTMLOptionElement HTMLOptionsCollection
+            HTMLParagraphElement HTMLParamElement HTMLPreElement
+            HTMLQuoteElement HTMLScriptElement HTMLSelectElement
+            HTMLStyleElement HTMLTableCaptionElement
+            HTMLTableCellElement HTMLTableColElement
+            HTMLTableElement HTMLTableRowElement
+            HTMLTableSectionElement HTMLTextAreaElement
+            HTMLTitleElement HTMLUListElement
+
+            ;; DOM level 3
+            DOMConfiguration DOMError DOMException
+            DOMImplementationList DOMImplementationSource
+            DOMLocator DOMStringList NameList TypeInfo
+            UserDataHandler
+
+            ;; Window
+            alert confirm document java navigator prompt screen
+            self top
+
+            ;; W3C CSS
+            CSSCharsetRule CSSFontFace CSSFontFaceRule
+            CSSImportRule CSSMediaRule CSSPageRule
+            CSSPrimitiveValue CSSProperties CSSRule CSSRuleList
+            CSSStyleDeclaration CSSStyleRule CSSStyleSheet
+            CSSValue CSSValueList Counter DOMImplementationCSS
+            DocumentCSS DocumentStyle ElementCSSInlineStyle
+            LinkStyle MediaList RGBColor Rect StyleSheet
+            StyleSheetList ViewCSS
+
+            ;; W3C Event
+            EventListener EventTarget Event DocumentEvent UIEvent
+            MouseEvent MutationEvent KeyboardEvent
+
+            ;; W3C Range
+            DocumentRange Range RangeException
+
+            ;; W3C XML
+            XPathResult XMLHttpRequest))
+  &quot;Browser externs.
+You can cause these to be included or excluded with the custom
+variable `js2-include-browser-externs'.&quot;)
+
+(defvar js2-rhino-externs
+  (mapcar 'symbol-name
+          '(Packages importClass importPackage com org java
+            ;; Global object (shell) externs
+            defineClass deserialize doctest gc help load
+            loadClass print quit readFile readUrl runCommand seal
+            serialize spawn sync toint32 version))
+  &quot;Mozilla Rhino externs.
+Set `js2-include-rhino-externs' to t to include them.&quot;)
+
+(defvar js2-gears-externs
+  (mapcar 'symbol-name
+          '(
+            ;; TODO(stevey):  add these
+            ))
+  &quot;Google Gears externs.
+Set `js2-include-gears-externs' to t to include them.&quot;)
+
+;;; Variables
+
+(defvar js2-emacs22 (&gt;= emacs-major-version 22))
+
+(defcustom js2-highlight-level 2
+  &quot;Amount of syntax highlighting to perform.
+0 or a negative value means do no highlighting.
+1 adds basic syntax highlighting.
+2 adds highlighting of some Ecma built-in properties.
+3 adds highlighting of many Ecma built-in functions.&quot;
+  :group 'js2-mode
+  :type '(choice (const :tag &quot;None&quot; 0)
+                 (const :tag &quot;Basic&quot; 1)
+                 (const :tag &quot;Include Properties&quot; 2)
+                 (const :tag &quot;Include Functions&quot; 3)))
+
+(defvar js2-mode-dev-mode-p nil
+  &quot;Non-nil if running in development mode.  Normally nil.&quot;)
+
+(defgroup js2-mode nil
+  &quot;An improved JavaScript mode.&quot;
+  :group 'languages)
+
+(defcustom js2-basic-offset (if (and (boundp 'c-basic-offset)
+                                     (numberp c-basic-offset))
+                                c-basic-offset
+                              4)
+  &quot;Number of spaces to indent nested statements.
+Similar to `c-basic-offset'.&quot;
+  :group 'js2-mode
+  :type 'integer)
+(make-variable-buffer-local 'js2-basic-offset)
+
+;; TODO(stevey):  move this code into a separate minor mode.
+(defcustom js2-mirror-mode nil
+  &quot;Non-nil to insert closing brackets, parens, etc. automatically.&quot;
+  :group 'js2-mode
+  :type 'boolean)
+
+(defcustom js2-auto-indent-p nil
+  &quot;Automatic indentation with punctuation characters.
+If non-nil, the current line is indented when certain punctuations
+are inserted.&quot;
+  :group 'js2-mode
+  :type 'boolean)
+
+(defcustom js2-bounce-indent-p nil
+  &quot;Non-nil to have indent-line function choose among alternatives.
+If nil, the indent-line function will indent to a predetermined column
+based on heuristic guessing.  If non-nil, then if the current line is
+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.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-indent-on-enter-key nil
+  &quot;Non-nil to have Enter/Return key indent the line.
+This is unusual for Emacs modes but common in IDEs like Eclipse.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-enter-indents-newline nil
+  &quot;Non-nil to have Enter/Return key indent the newly-inserted line.
+This is unusual for Emacs modes but common in IDEs like Eclipse.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-rebind-eol-bol-keys t
+  &quot;Non-nil to rebind `beginning-of-line' and `end-of-line' keys.
+If non-nil, bounce between bol/eol and first/last non-whitespace char.&quot;
+  :group 'js2-mode
+  :type 'boolean)
+
+(defcustom js2-electric-keys '(&quot;{&quot; &quot;}&quot; &quot;(&quot; &quot;)&quot; &quot;[&quot; &quot;]&quot; &quot;:&quot; &quot;;&quot; &quot;,&quot; &quot;*&quot;)
+  &quot;Keys that auto-indent when `js2-auto-indent-p' is non-nil.
+Each value in the list is passed to `define-key'.&quot;
+  :type 'list
+  :group 'js2-mode)
+
+(defcustom js2-idle-timer-delay 0.2
+  &quot;Delay in secs before re-parsing after user makes changes.
+Multiplied by `js2-dynamic-idle-timer-adjust', which see.&quot;
+  :type 'number
+  :group 'js2-mode)
+(make-variable-buffer-local 'js2-idle-timer-delay)
+
+(defcustom js2-dynamic-idle-timer-adjust 0
+  &quot;Positive to adjust `js2-idle-timer-delay' based on file size.
+The idea is that for short files, parsing is faster so we can be
+more responsive to user edits without interfering with editing.
+The buffer length in characters (typically bytes) is divided by
+this value and used to multiply `js2-idle-timer-delay' for the
+buffer.  For example, a 21k file and 10k adjust yields 21k/10k
+== 2, so js2-idle-timer-delay is multiplied by 2.
+If `js2-dynamic-idle-timer-adjust' is 0 or negative,
+`js2-idle-timer-delay' is not dependent on the file size.&quot;
+  :type 'number
+  :group 'js2-mode)
+
+(defcustom js2-mode-escape-quotes t
+  &quot;Non-nil to disable automatic quote-escaping inside strings.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-mode-squeeze-spaces t
+  &quot;Non-nil to normalize whitespace when filling in comments.
+Multiple runs of spaces are converted to a single space.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-mode-show-parse-errors t
+  &quot;True to highlight parse errors.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-mode-show-strict-warnings t
+  &quot;Non-nil to emit Ecma strict-mode warnings.
+Some of the warnings can be individually disabled by other flags,
+even if this flag is non-nil.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-strict-trailing-comma-warning t
+  &quot;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.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-strict-missing-semi-warning t
+  &quot;Non-nil to warn about semicolon auto-insertion after statement.
+Technically this is legal per Ecma-262, but some style guides disallow
+depending on it.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-missing-semi-one-line-override nil
+  &quot;Non-nil to permit missing semicolons in one-line functions.
+In one-liner functions such as `function identity(x) {return x}'
+people often omit the semicolon for a cleaner look.  If you are
+such a person, you can suppress the missing-semicolon warning
+by setting this variable to t.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-strict-inconsistent-return-warning t
+  &quot;Non-nil to warn about mixing returns with value-returns.
+It's perfectly legal to have a `return' and a `return foo' in the
+same function, but it's often an indicator of a bug, and it also
+interferes with type inference (in systems that support it.)&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-strict-cond-assign-warning t
+  &quot;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)) ...&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-strict-cond-assign-warning t
+  &quot;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)) ...&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-strict-var-redeclaration-warning t
+  &quot;Non-nil to warn about redeclaring variables in a script or function.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-strict-var-hides-function-arg-warning t
+  &quot;Non-nil to warn about a var decl hiding a function argument.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-skip-preprocessor-directives nil
+  &quot;Non-nil to treat lines beginning with # as comments.
+Useful for viewing Mozilla JavaScript source code.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-language-version 180
+  &quot;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.&quot;
+  :type 'integer
+  :group 'js2-mode)
+
+(defcustom js2-allow-keywords-as-property-names t
+  &quot;If non-nil, you can use JavaScript keywords as object property names.
+Examples:
+
+  var foo = {int: 5, while: 6, continue: 7};
+  foo.return = 8;
+
+Ecma-262 forbids this syntax, but many browsers support it.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-instanceof-has-side-effects nil
+  &quot;If non-nil, treats the instanceof operator as having side effects.
+This is useful for xulrunner apps.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-cleanup-whitespace nil
+  &quot;Non-nil to invoke `delete-trailing-whitespace' before saves.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-move-point-on-right-click t
+  &quot;Non-nil to move insertion point when you right-click.
+This makes right-click context menu behavior a bit more intuitive,
+since menu operations generally apply to the point.  The exception
+is if there is a region selection, in which case the point does -not-
+move, so cut/copy/paste etc. can work properly.
+
+Note that IntelliJ moves the point, and Eclipse leaves it alone,
+so this behavior is customizable.&quot;
+  :group 'js2-mode
+  :type 'boolean)
+
+(defcustom js2-allow-rhino-new-expr-initializer t
+  &quot;Non-nil to support a Rhino's experimental syntactic construct.
+
+Rhino supports the ability to follow a `new' expression with an object
+literal, which is used to set additional properties on the new object
+after calling its constructor.  Syntax:
+
+  new &lt;expr&gt; [ ( arglist ) ] [initializer]
+
+Hence, this expression:
+
+  new Object {a: 1, b: 2}
+
+results in an Object with properties a=1 and b=2.  This syntax is
+apparently not configurable in Rhino - it's currently always enabled,
+as of Rhino version 1.7R2.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-allow-member-expr-as-function-name nil
+  &quot;Non-nil to support experimental Rhino syntax for function names.
+
+Rhino supports an experimental syntax configured via the Rhino Context
+setting `allowMemberExprAsFunctionName'.  The experimental syntax is:
+
+  function &lt;member-expr&gt; ( [ arg-list ] ) { &lt;body&gt; }
+
+Where member-expr is a non-parenthesized 'member expression', which
+is anything at the grammar level of a new-expression or lower, meaning
+any expression that does not involve infix or unary operators.
+
+When &lt;member-expr&gt; is not a simple identifier, then it is syntactic
+sugar for assigning the anonymous function to the &lt;member-expr&gt;.  Hence,
+this code:
+
+  function a.b().c[2] (x, y) { ... }
+
+is rewritten as:
+
+  a.b().c[2] = function(x, y) {...}
+
+which doesn't seem particularly useful, but Rhino permits it.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defvar js2-mode-version 20090727
+  &quot;Release number for `js2-mode'.&quot;)
+
+;; scanner variables
+
+(defmacro js2-deflocal (name value &amp;optional comment)
+  &quot;Define a buffer-local variable NAME with VALUE and COMMENT.&quot;
+  `(progn
+     (defvar ,name ,value ,comment)
+     (make-variable-buffer-local ',name)))
+
+;; We record the start and end position of each token.
+(js2-deflocal js2-token-beg 1)
+(js2-deflocal js2-token-end -1)
+
+(defvar js2-EOF_CHAR -1
+  &quot;Represents end of stream.  Distinct from js2-EOF token type.&quot;)
+
+;; I originally used symbols to represent tokens, but Rhino uses
+;; ints and then sets various flag bits in them, so ints it is.
+;; The upshot is that we need a `js2-' prefix in front of each name.
+(defvar js2-ERROR -1)
+(defvar js2-EOF 0)
+(defvar js2-EOL 1)
+(defvar js2-ENTERWITH 2)       ; begin interpreter bytecodes
+(defvar js2-LEAVEWITH 3)
+(defvar js2-RETURN 4)
+(defvar js2-GOTO 5)
+(defvar js2-IFEQ 6)
+(defvar js2-IFNE 7)
+(defvar js2-SETNAME 8)
+(defvar js2-BITOR 9)
+(defvar js2-BITXOR 10)
+(defvar js2-BITAND 11)
+(defvar js2-EQ 12)
+(defvar js2-NE 13)
+(defvar js2-LT 14)
+(defvar js2-LE 15)
+(defvar js2-GT 16)
+(defvar js2-GE 17)
+(defvar js2-LSH 18)
+(defvar js2-RSH 19)
+(defvar js2-URSH 20)
+(defvar js2-ADD 21)            ; infix plus
+(defvar js2-SUB 22)            ; infix minus
+(defvar js2-MUL 23)
+(defvar js2-DIV 24)
+(defvar js2-MOD 25)
+(defvar js2-NOT 26)
+(defvar js2-BITNOT 27)
+(defvar js2-POS 28)            ; unary plus
+(defvar js2-NEG 29)            ; unary minus
+(defvar js2-NEW 30)
+(defvar js2-DELPROP 31)
+(defvar js2-TYPEOF 32)
+(defvar js2-GETPROP 33)
+(defvar js2-GETPROPNOWARN 34)
+(defvar js2-SETPROP 35)
+(defvar js2-GETELEM 36)
+(defvar js2-SETELEM 37)
+(defvar js2-CALL 38)
+(defvar js2-NAME 39)           ; an identifier
+(defvar js2-NUMBER 40)
+(defvar js2-STRING 41)
+(defvar js2-NULL 42)
+(defvar js2-THIS 43)
+(defvar js2-FALSE 44)
+(defvar js2-TRUE 45)
+(defvar js2-SHEQ 46)           ; shallow equality (===)
+(defvar js2-SHNE 47)           ; shallow inequality (!==)
+(defvar js2-REGEXP 48)
+(defvar js2-BINDNAME 49)
+(defvar js2-THROW 50)
+(defvar js2-RETHROW 51)        ; rethrow caught exception: catch (e if ) uses it
+(defvar js2-IN 52)
+(defvar js2-INSTANCEOF 53)
+(defvar js2-LOCAL_LOAD 54)
+(defvar js2-GETVAR 55)
+(defvar js2-SETVAR 56)
+(defvar js2-CATCH_SCOPE 57)
+(defvar js2-ENUM_INIT_KEYS 58)
+(defvar js2-ENUM_INIT_VALUES 59)
+(defvar js2-ENUM_INIT_ARRAY 60)
+(defvar js2-ENUM_NEXT 61)
+(defvar js2-ENUM_ID 62)
+(defvar js2-THISFN 63)
+(defvar js2-RETURN_RESULT 64)  ; to return previously stored return result
+(defvar js2-ARRAYLIT 65)       ; array literal
+(defvar js2-OBJECTLIT 66)      ; object literal
+(defvar js2-GET_REF 67)        ; *reference
+(defvar js2-SET_REF 68)        ; *reference = something
+(defvar js2-DEL_REF 69)        ; delete reference
+(defvar js2-REF_CALL 70)       ; f(args) = something or f(args)++
+(defvar js2-REF_SPECIAL 71)    ; reference for special properties like __proto
+(defvar js2-YIELD 72)          ; JS 1.7 yield pseudo keyword
+
+;; XML support
+(defvar js2-DEFAULTNAMESPACE 73)
+(defvar js2-ESCXMLATTR 74)
+(defvar js2-ESCXMLTEXT 75)
+(defvar js2-REF_MEMBER 76)     ; Reference for x.@y, x..y etc.
+(defvar js2-REF_NS_MEMBER 77)  ; Reference for x.ns::y, x..ns::y etc.
+(defvar js2-REF_NAME 78)       ; Reference for @y, @[y] etc.
+(defvar js2-REF_NS_NAME 79)    ; Reference for ns::y, @ns::y@[y] etc.
+
+(defvar js2-first-bytecode js2-ENTERWITH)
+(defvar js2-last-bytecode js2-REF_NS_NAME)
+
+(defvar js2-TRY 80)
+(defvar js2-SEMI 81)           ; semicolon
+(defvar js2-LB 82)             ; left and right brackets
+(defvar js2-RB 83)
+(defvar js2-LC 84)             ; left and right curly-braces
+(defvar js2-RC 85)
+(defvar js2-LP 86)             ; left and right parens
+(defvar js2-RP 87)
+(defvar js2-COMMA 88)          ; comma operator
+
+(defvar js2-ASSIGN 89)         ; simple assignment (=)
+(defvar js2-ASSIGN_BITOR 90)   ; |=
+(defvar js2-ASSIGN_BITXOR 91)  ; ^=
+(defvar js2-ASSIGN_BITAND 92)  ; &amp;=
+(defvar js2-ASSIGN_LSH 93)     ; &lt;&lt;=
+(defvar js2-ASSIGN_RSH 94)     ; &gt;&gt;=
+(defvar js2-ASSIGN_URSH 95)    ; &gt;&gt;&gt;=
+(defvar js2-ASSIGN_ADD 96)     ; +=
+(defvar js2-ASSIGN_SUB 97)     ; -=
+(defvar js2-ASSIGN_MUL 98)     ; *=
+(defvar js2-ASSIGN_DIV 99)     ; /=
+(defvar js2-ASSIGN_MOD 100)    ; %=
+
+(defvar js2-first-assign js2-ASSIGN)
+(defvar js2-last-assign js2-ASSIGN_MOD)
+
+(defvar js2-HOOK 101)          ; conditional (?:)
+(defvar js2-COLON 102)
+(defvar js2-OR 103)            ; logical or (||)
+(defvar js2-AND 104)           ; logical and (&amp;&amp;)
+(defvar js2-INC 105)           ; increment/decrement (++ --)
+(defvar js2-DEC 106)
+(defvar js2-DOT 107)           ; member operator (.)
+(defvar js2-FUNCTION 108)      ; function keyword
+(defvar js2-EXPORT 109)        ; export keyword
+(defvar js2-IMPORT 110)        ; import keyword
+(defvar js2-IF 111)            ; if keyword
+(defvar js2-ELSE 112)          ; else keyword
+(defvar js2-SWITCH 113)        ; switch keyword
+(defvar js2-CASE 114)          ; case keyword
+(defvar js2-DEFAULT 115)       ; default keyword
+(defvar js2-WHILE 116)         ; while keyword
+(defvar js2-DO 117)            ; do keyword
+(defvar js2-FOR 118)           ; for keyword
+(defvar js2-BREAK 119)         ; break keyword
+(defvar js2-CONTINUE 120)      ; continue keyword
+(defvar js2-VAR 121)           ; var keyword
+(defvar js2-WITH 122)          ; with keyword
+(defvar js2-CATCH 123)         ; catch keyword
+(defvar js2-FINALLY 124)       ; finally keyword
+(defvar js2-VOID 125)          ; void keyword
+(defvar js2-RESERVED 126)      ; reserved keywords
+
+(defvar js2-EMPTY 127)
+
+;; Types used for the parse tree - never returned by scanner.
+
+(defvar js2-BLOCK 128)         ; statement block
+(defvar js2-LABEL 129)         ; label
+(defvar js2-TARGET 130)
+(defvar js2-LOOP 131)
+(defvar js2-EXPR_VOID 132)     ; expression statement in functions
+(defvar js2-EXPR_RESULT 133)   ; expression statement in scripts
+(defvar js2-JSR 134)
+(defvar js2-SCRIPT 135)        ; top-level node for entire script
+(defvar js2-TYPEOFNAME 136)    ; for typeof(simple-name)
+(defvar js2-USE_STACK 137)
+(defvar js2-SETPROP_OP 138)    ; x.y op= something
+(defvar js2-SETELEM_OP 139)    ; x[y] op= something
+(defvar js2-LOCAL_BLOCK 140)
+(defvar js2-SET_REF_OP 141)    ; *reference op= something
+
+;; For XML support:
+(defvar js2-DOTDOT 142)        ; member operator (..)
+(defvar js2-COLONCOLON 143)    ; namespace::name
+(defvar js2-XML 144)           ; XML type
+(defvar js2-DOTQUERY 145)      ; .() -- e.g., x.emps.emp.(name == &quot;terry&quot;)
+(defvar js2-XMLATTR 146)       ; @
+(defvar js2-XMLEND 147)
+
+;; Optimizer-only tokens
+(defvar js2-TO_OBJECT 148)
+(defvar js2-TO_DOUBLE 149)
+
+(defvar js2-GET 150)           ; JS 1.5 get pseudo keyword
+(defvar js2-SET 151)           ; JS 1.5 set pseudo keyword
+(defvar js2-LET 152)           ; JS 1.7 let pseudo keyword
+(defvar js2-CONST 153)
+(defvar js2-SETCONST 154)
+(defvar js2-SETCONSTVAR 155)
+(defvar js2-ARRAYCOMP 156)
+(defvar js2-LETEXPR 157)
+(defvar js2-WITHEXPR 158)
+(defvar js2-DEBUGGER 159)
+
+(defvar js2-COMMENT 160)
+(defvar js2-ENUM 161)  ; for &quot;enum&quot; reserved word
+
+(defconst js2-num-tokens (1+ js2-ENUM))
+
+(defconst js2-debug-print-trees nil)
+
+;; Rhino accepts any string or stream as input.  Emacs character
+;; processing works best in buffers, so we'll assume the input is a
+;; buffer.  JavaScript strings can be copied into temp buffers before
+;; scanning them.
+
+;; Buffer-local variables yield much cleaner code than using `defstruct'.
+;; They're the Emacs equivalent of instance variables, more or less.
+
+(js2-deflocal js2-ts-dirty-line nil
+  &quot;Token stream buffer-local variable.
+Indicates stuff other than whitespace since start of line.&quot;)
+
+(js2-deflocal js2-ts-regexp-flags nil
+  &quot;Token stream buffer-local variable.&quot;)
+
+(js2-deflocal js2-ts-string &quot;&quot;
+  &quot;Token stream buffer-local variable.
+Last string scanned.&quot;)
+
+(js2-deflocal js2-ts-number nil
+  &quot;Token stream buffer-local variable.
+Last literal number scanned.&quot;)
+
+(js2-deflocal js2-ts-hit-eof nil
+  &quot;Token stream buffer-local variable.&quot;)
+
+(js2-deflocal js2-ts-line-start 0
+  &quot;Token stream buffer-local variable.&quot;)
+
+(js2-deflocal js2-ts-lineno 1
+  &quot;Token stream buffer-local variable.&quot;)
+
+(js2-deflocal js2-ts-line-end-char -1
+  &quot;Token stream buffer-local variable.&quot;)
+
+(js2-deflocal js2-ts-cursor 1  ; emacs buffers are 1-indexed
+  &quot;Token stream buffer-local variable.
+Current scan position.&quot;)
+
+(js2-deflocal js2-ts-is-xml-attribute nil
+  &quot;Token stream buffer-local variable.&quot;)
+
+(js2-deflocal js2-ts-xml-is-tag-content nil
+  &quot;Token stream buffer-local variable.&quot;)
+
+(js2-deflocal js2-ts-xml-open-tags-count 0
+  &quot;Token stream buffer-local variable.&quot;)
+
+(js2-deflocal js2-ts-string-buffer nil
+  &quot;Token stream buffer-local variable.
+List of chars built up while scanning various tokens.&quot;)
+
+(js2-deflocal js2-ts-comment-type nil
+  &quot;Token stream buffer-local variable.&quot;)
+
+;;; Parser variables
+
+(js2-deflocal js2-parsed-errors nil
+  &quot;List of errors produced during scanning/parsing.&quot;)
+
+(js2-deflocal js2-parsed-warnings nil
+  &quot;List of warnings produced during scanning/parsing.&quot;)
+
+(js2-deflocal js2-recover-from-parse-errors t
+  &quot;Non-nil to continue parsing after a syntax error.
+
+In recovery mode, the AST will be built in full, and any error
+nodes will be flagged with appropriate error information.  If
+this flag is nil, a syntax error will result in an error being
+signaled.
+
+The variable is automatically buffer-local, because different
+modes that use the parser will need different settings.&quot;)
+
+(js2-deflocal js2-parse-hook nil
+  &quot;List of callbacks for receiving parsing progress.&quot;)
+
+(defvar js2-parse-finished-hook nil
+  &quot;List of callbacks to notify when parsing finishes.
+Not called if parsing was interrupted.&quot;)
+
+(js2-deflocal js2-is-eval-code nil
+  &quot;True if we're evaluating code in a string.
+If non-nil, the tokenizer will record the token text, and the AST nodes
+will record their source text.  Off by default for IDE modes, since the
+text is available in the buffer.&quot;)
+
+(defvar js2-parse-ide-mode t
+  &quot;Non-nil if the parser is being used for `js2-mode'.
+If non-nil, the parser will set text properties for fontification
+and the syntax table.  The value should be nil when using the
+parser as a frontend to an interpreter or byte compiler.&quot;)
+
+;;; Parser instance variables (buffer-local vars for js2-parse)
+
+(defconst js2-clear-ti-mask #xFFFF
+  &quot;Mask to clear token information bits.&quot;)
+
+(defconst js2-ti-after-eol (lsh 1 16)
+  &quot;Flag:  first token of the source line.&quot;)
+
+(defconst js2-ti-check-label (lsh 1 17)
+  &quot;Flag:  indicates to check for label.&quot;)
+
+;; Inline Rhino's CompilerEnvirons vars as buffer-locals.
+
+(js2-deflocal js2-compiler-generate-debug-info t)
+(js2-deflocal js2-compiler-use-dynamic-scope nil)
+(js2-deflocal js2-compiler-reserved-keywords-as-identifier nil)
+(js2-deflocal js2-compiler-xml-available t)
+(js2-deflocal js2-compiler-optimization-level 0)
+(js2-deflocal js2-compiler-generating-source t)
+(js2-deflocal js2-compiler-strict-mode nil)
+(js2-deflocal js2-compiler-report-warning-as-error nil)
+(js2-deflocal js2-compiler-generate-observer-count nil)
+(js2-deflocal js2-compiler-activation-names nil)
+
+;; SKIP:  sourceURI
+
+;; There's a compileFunction method in Context.java - may need it.
+(js2-deflocal js2-called-by-compile-function nil
+  &quot;True if `js2-parse' was called by `js2-compile-function'.
+Will only be used when we finish implementing the interpreter.&quot;)
+
+;; SKIP:  ts  (we just call `js2-init-scanner' and use its vars)
+
+(js2-deflocal js2-current-flagged-token js2-EOF)
+(js2-deflocal js2-current-token js2-EOF)
+
+;; SKIP:  node factory - we're going to just call functions directly,
+;; and eventually go to a unified AST format.
+
+(js2-deflocal js2-nesting-of-function 0)
+
+(js2-deflocal js2-recorded-assignments nil
+  &quot;Tracks assignments found during parsing.&quot;)
+
+(defcustom js2-global-externs nil
+  &quot;A list of any extern names you'd like to consider always declared.
+This list is global and is used by all js2-mode files.
+You can create buffer-local externs list using `js2-additional-externs'.
+
+There is also a buffer-local variable `js2-default-externs',
+which is initialized by default to include the Ecma-262 externs
+and the standard browser externs.  The three lists are all
+checked during highlighting.&quot;
+  :type 'list
+  :group 'js2-mode)
+
+(js2-deflocal js2-default-externs nil
+  &quot;Default external declarations.
+
+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.
+
+See `js2-additional-externs' for more information.&quot;)
+
+(defcustom js2-include-browser-externs t
+  &quot;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.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-include-rhino-externs t
+  &quot;Non-nil to include Mozilla Rhino externs in the master externs list.
+See `js2-additional-externs' for more information about externs.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-include-gears-externs t
+  &quot;Non-nil to include Google Gears externs in the master externs list.
+See `js2-additional-externs' for more information about externs.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(js2-deflocal js2-additional-externs nil
+  &quot;A buffer-local list of additional external declarations.
+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
+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.
+
+You may want to declare your externs in three ways.
+First, you can add externs that are valid for all your JavaScript files.
+You should probably do this by adding them to `js2-global-externs', which
+is a global list used for all js2-mode files.
+
+Next, you can add a function to `js2-mode-hook' that adds additional
+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
+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
+other externs, possibly tied to your build system.  These should also
+be added to `js2-additional-externs'.
+
+Your post-parse callback may of course also use the simpler and
+faster (but perhaps less robust) approach of simply scanning the
+buffer text for your imports, using regular expressions.&quot;)
+
+;; SKIP:  decompiler
+;; SKIP:  encoded-source
+
+;;; The following variables are per-function and should be saved/restored
+;;; during function parsing...
+
+(js2-deflocal js2-current-script-or-fn nil)
+(js2-deflocal js2-current-scope nil)
+(js2-deflocal js2-nesting-of-with 0)
+(js2-deflocal js2-label-set nil
+  &quot;An alist mapping label names to nodes.&quot;)
+
+(js2-deflocal js2-loop-set nil)
+(js2-deflocal js2-loop-and-switch-set nil)
+(js2-deflocal js2-has-return-value nil)
+(js2-deflocal js2-end-flags 0)
+
+;;; ...end of per function variables
+
+;; Without 2-token lookahead, labels are a problem.
+;; These vars store the token info of the last matched name,
+;; iff it wasn't the last matched token.  Only valid in some contexts.
+(defvar js2-prev-name-token-start nil)
+(defvar js2-prev-name-token-string nil)
+
+(defsubst js2-save-name-token-data (pos name)
+  (setq js2-prev-name-token-start pos
+        js2-prev-name-token-string name))
+
+;; These flags enumerate the possible ways a statement/function can
+;; terminate. These flags are used by endCheck() and by the Parser to
+;; detect inconsistent return usage.
+;;
+;; END_UNREACHED is reserved for code paths that are assumed to always be
+;; able to execute (example: throw, continue)
+;;
+;; END_DROPS_OFF indicates if the statement can transfer control to the
+;; next one. Statement such as return dont. A compound statement may have
+;; some branch that drops off control to the next statement.
+;;
+;; END_RETURNS indicates that the statement can return (without arguments)
+;; END_RETURNS_VALUE indicates that the statement can return a value.
+;;
+;; A compound statement such as
+;; if (condition) {
+;;   return value;
+;; }
+;; Will be detected as (END_DROPS_OFF | END_RETURN_VALUE) by endCheck()
+
+(defconst js2-end-unreached     #x0)
+(defconst js2-end-drops-off     #x1)
+(defconst js2-end-returns       #x2)
+(defconst js2-end-returns-value #x4)
+(defconst js2-end-yields        #x8)
+
+;; Rhino awkwardly passes a statementLabel parameter to the
+;; statementHelper() function, the main statement parser, which
+;; is then used by quite a few of the sub-parsers.  We just make
+;; it a buffer-local variable and make sure it's cleaned up properly.
+(js2-deflocal js2-labeled-stmt nil)  ; type `js2-labeled-stmt-node'
+
+;; Similarly, Rhino passes an inForInit boolean through about half
+;; the expression parsers.  We use a dynamically-scoped variable,
+;; which makes it easier to funcall the parsers individually without
+;; worrying about whether they take the parameter or not.
+(js2-deflocal js2-in-for-init nil)
+(js2-deflocal js2-temp-name-counter 0)
+(js2-deflocal js2-parse-stmt-count 0)
+
+(defsubst js2-get-next-temp-name ()
+  (format &quot;$%d&quot; (incf js2-temp-name-counter)))
+
+(defvar js2-parse-interruptable-p t
+  &quot;Set this to nil to force parse to continue until finished.
+This will mostly be useful for interpreters.&quot;)
+
+(defvar js2-statements-per-pause 50
+  &quot;Pause after this many statements to check for user input.
+If user input is pending, stop the parse and discard the tree.
+This makes for a smoother user experience for large files.
+You may have to wait a second or two before the highlighting
+and error-reporting appear, but you can always type ahead if
+you wish.  This appears to be more or less how Eclipse, IntelliJ
+and other editors work.&quot;)
+
+(js2-deflocal js2-record-comments t
+  &quot;Instructs the scanner to record comments in `js2-scanned-comments'.&quot;)
+
+(js2-deflocal js2-scanned-comments nil
+  &quot;List of all comments from the current parse.&quot;)
+
+(defcustom js2-mode-indent-inhibit-undo nil
+  &quot;Non-nil to disable collection of Undo information when indenting lines.
+Some users have requested this behavior.  It's nil by default because
+other Emacs modes don't work this way.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-mode-indent-ignore-first-tab nil
+  &quot;If non-nil, ignore first TAB keypress if we look indented properly.
+It's fairly common for users to navigate to an already-indented line
+and press TAB for reassurance that it's been indented.  For this class
+of users, we want the first TAB press on a line to be ignored if the
+line is already indented to one of the precomputed alternatives.
+
+This behavior is only partly implemented.  If you TAB-indent a line,
+navigate to another line, and then navigate back, it fails to clear
+the last-indented variable, so it thinks you've already hit TAB once,
+and performs the indent.  A full solution would involve getting on the
+point-motion hooks for the entire buffer.  If we come across another
+use cases that requires watching point motion, I'll consider doing it.
+
+If you set this variable to nil, then the TAB key will always change
+the indentation of the current line, if more than one alternative
+indentation spot exists.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defvar js2-indent-hook nil
+  &quot;A hook for user-defined indentation rules.
+
+Functions on this hook should expect two arguments:    (LIST INDEX)
+The LIST argument is the list of computed indentation points for
+the current line.  INDEX is the list index of the indentation point
+that `js2-bounce-indent' plans to use.  If INDEX is nil, then the
+indent function is not going to change the current line indentation.
+
+If a hook function on this list returns a non-nil value, then
+`js2-bounce-indent' assumes the hook function has performed its own
+indentation, and will do nothing.  If all hook functions on the list
+return nil, then `js2-bounce-indent' will use its computed indentation
+and reindent the line.
+
+When hook functions on this hook list are called, the variable
+`js2-mode-ast' may or may not be set, depending on whether the
+parse tree is available.  If the variable is nil, you can pass a
+callback to `js2-mode-wait-for-parse', and your callback will be
+called after the new parse tree is built.  This can take some time
+in large files.&quot;)
+
+(defface js2-warning-face
+  `((((class color) (background light))
+     (:underline  &quot;orange&quot;))
+    (((class color) (background dark))
+     (:underline &quot;orange&quot;))
+    (t (:underline t)))
+  &quot;Face for JavaScript warnings.&quot;
+  :group 'js2-mode)
+
+(defface js2-error-face
+  `((((class color) (background light))
+     (:foreground &quot;red&quot;))
+    (((class color) (background dark))
+     (:foreground &quot;red&quot;))
+    (t (:foreground &quot;red&quot;)))
+  &quot;Face for JavaScript errors.&quot;
+  :group 'js2-mode)
+
+(defface js2-jsdoc-tag-face
+  '((t :foreground &quot;SlateGray&quot;))
+  &quot;Face used to highlight @whatever tags in jsdoc comments.&quot;
+  :group 'js2-mode)
+
+(defface js2-jsdoc-type-face
+  '((t :foreground &quot;SteelBlue&quot;))
+  &quot;Face used to highlight {FooBar} types in jsdoc comments.&quot;
+  :group 'js2-mode)
+
+(defface js2-jsdoc-value-face
+  '((t :foreground &quot;PeachPuff3&quot;))
+  &quot;Face used to highlight tag values in jsdoc comments.&quot;
+  :group 'js2-mode)
+
+(defface js2-function-param-face
+  '((t :foreground &quot;SeaGreen&quot;))
+  &quot;Face used to highlight function parameters in javascript.&quot;
+  :group 'js2-mode)
+
+(defface js2-instance-member-face
+  '((t :foreground &quot;DarkOrchid&quot;))
+  &quot;Face used to highlight instance variables in javascript.
+Not currently used.&quot;
+  :group 'js2-mode)
+
+(defface js2-private-member-face
+  '((t :foreground &quot;PeachPuff3&quot;))
+  &quot;Face used to highlight calls to private methods in javascript.
+Not currently used.&quot;
+  :group 'js2-mode)
+
+(defface js2-private-function-call-face
+  '((t :foreground &quot;goldenrod&quot;))
+  &quot;Face used to highlight calls to private functions in javascript.
+Not currently used.&quot;
+  :group 'js2-mode)
+
+(defface js2-jsdoc-html-tag-name-face
+  (if js2-emacs22
+      '((((class color) (min-colors 88) (background light))
+         (:foreground &quot;rosybrown&quot;))
+        (((class color) (min-colors 8) (background dark))
+         (:foreground &quot;yellow&quot;))
+        (((class color) (min-colors 8) (background light))
+         (:foreground &quot;magenta&quot;)))
+    '((((type tty pc) (class color) (background light))
+       (:foreground &quot;magenta&quot;))
+      (((type tty pc) (class color) (background dark))
+       (:foreground &quot;yellow&quot;))
+      (t (:foreground &quot;RosyBrown&quot;))))
+    &quot;Face used to highlight jsdoc html tag names&quot;
+  :group 'js2-mode)
+
+(defface js2-jsdoc-html-tag-delimiter-face
+  (if js2-emacs22
+      '((((class color) (min-colors 88) (background light))
+         (:foreground &quot;dark khaki&quot;))
+        (((class color) (min-colors 8) (background dark))
+         (:foreground &quot;green&quot;))
+        (((class color) (min-colors 8) (background light))
+         (:foreground &quot;green&quot;)))
+    '((((type tty pc) (class color) (background light))
+       (:foreground &quot;green&quot;))
+      (((type tty pc) (class color) (background dark))
+       (:foreground &quot;green&quot;))
+      (t (:foreground &quot;dark khaki&quot;))))
+  &quot;Face used to highlight brackets in jsdoc html tags.&quot;
+  :group 'js2-mode)
+
+(defface js2-magic-paren-face
+  '((t :underline t))
+  &quot;Face used to color parens that will be auto-overwritten.&quot;
+  :group 'js2-mode)
+
+(defcustom js2-post-parse-callbacks nil
+  &quot;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.&quot;
+  :type 'list
+  :group 'js2-mode)
+
+(defface js2-external-variable-face
+  '((t :foreground &quot;orange&quot;))
+  &quot;Face used to highlight assignments to undeclared variables.
+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
+another file, or you've got a potential bug.&quot;
+  :group 'js2-mode)
+
+(defcustom js2-highlight-external-variables t
+  &quot;Non-nil to higlight assignments to undeclared variables.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defcustom js2-auto-insert-catch-block t
+  &quot;Non-nil to insert matching catch block on open-curly after `try'.&quot;
+  :type 'boolean
+  :group 'js2-mode)
+
+(defvar js2-mode-map
+  (let ((map (make-sparse-keymap))
+        keys)
+    (define-key map [mouse-1] #'js2-mode-show-node)
+    (define-key map (kbd &quot;C-m&quot;) #'js2-enter-key)
+    (when js2-rebind-eol-bol-keys
+      (define-key map (kbd &quot;C-a&quot;) #'js2-beginning-of-line)
+      (define-key map (kbd &quot;C-e&quot;) #'js2-end-of-line))
+    (define-key map (kbd &quot;C-c C-e&quot;) #'js2-mode-hide-element)
+    (define-key map (kbd &quot;C-c C-s&quot;) #'js2-mode-show-element)
+    (define-key map (kbd &quot;C-c C-a&quot;) #'js2-mode-show-all)
+    (define-key map (kbd &quot;C-c C-f&quot;) #'js2-mode-toggle-hide-functions)
+    (define-key map (kbd &quot;C-c C-t&quot;) #'js2-mode-toggle-hide-comments)
+    (define-key map (kbd &quot;C-c C-o&quot;) #'js2-mode-toggle-element)
+    (define-key map (kbd &quot;C-c C-w&quot;) #'js2-mode-toggle-warnings-and-errors)
+    (define-key map (kbd &quot;C-c C-`&quot;) #'js2-next-error)
+    ;; also define user's preference for next-error, if available
+    (if (setq keys (where-is-internal #'next-error))
+        (define-key map (car keys) #'js2-next-error))
+    (define-key map (or (car (where-is-internal #'mark-defun))
+                        (kbd &quot;M-C-h&quot;))
+      #'js2-mark-defun)
+    (define-key map (or (car (where-is-internal #'narrow-to-defun))
+                        (kbd &quot;C-x nd&quot;))
+      #'js2-narrow-to-defun)
+    (define-key map [down-mouse-3] #'js2-down-mouse-3)
+    (when js2-auto-indent-p
+      (mapc (lambda (key)
+              (define-key map key #'js2-insert-and-indent))
+            js2-electric-keys))
+
+    (define-key map [menu-bar javascript]
+      (cons &quot;JavaScript&quot; (make-sparse-keymap &quot;JavaScript&quot;)))
+
+    (define-key map [menu-bar javascript customize-js2-mode]
+      '(menu-item &quot;Customize js2-mode&quot; js2-mode-customize
+                  :help &quot;Customize the behavior of this mode&quot;))
+
+    (define-key map [menu-bar javascript js2-force-refresh]
+      '(menu-item &quot;Force buffer refresh&quot; js2-mode-reset
+                  :help &quot;Re-parse the buffer from scratch&quot;))
+
+    (define-key map [menu-bar javascript separator-2]
+      '(&quot;--&quot;))
+
+    (define-key map [menu-bar javascript next-error]
+      '(menu-item &quot;Next warning or error&quot; js2-next-error
+                  :enabled (and js2-mode-ast
+                                (or (js2-ast-root-errors js2-mode-ast)
+                                    (js2-ast-root-warnings js2-mode-ast)))
+                  :help &quot;Move to next warning or error&quot;))
+
+    (define-key map [menu-bar javascript display-errors]
+      '(menu-item &quot;Show errors and warnings&quot; js2-mode-display-warnings-and-errors
+                  :visible (not js2-mode-show-parse-errors)
+                  :help &quot;Turn on display of warnings and errors&quot;))
+
+    (define-key map [menu-bar javascript hide-errors]
+      '(menu-item &quot;Hide errors and warnings&quot; js2-mode-hide-warnings-and-errors
+                  :visible js2-mode-show-parse-errors
+                  :help &quot;Turn off display of warnings and errors&quot;))
+
+    (define-key map [menu-bar javascript separator-1]
+      '(&quot;--&quot;))
+
+    (define-key map [menu-bar javascript js2-toggle-function]
+      '(menu-item &quot;Show/collapse element&quot; js2-mode-toggle-element
+                  :help &quot;Hide or show function body or comment&quot;))
+
+    (define-key map [menu-bar javascript show-comments]
+      '(menu-item &quot;Show block comments&quot; js2-mode-toggle-hide-comments
+                  :visible js2-mode-comments-hidden
+                  :help &quot;Expand all hidden block comments&quot;))
+
+    (define-key map [menu-bar javascript hide-comments]
+      '(menu-item &quot;Hide block comments&quot; js2-mode-toggle-hide-comments
+                  :visible (not js2-mode-comments-hidden)
+                  :help &quot;Show block comments as /*...*/&quot;))
+
+    (define-key map [menu-bar javascript show-all-functions]
+      '(menu-item &quot;Show function bodies&quot; js2-mode-toggle-hide-functions
+                  :visible js2-mode-functions-hidden
+                  :help &quot;Expand all hidden function bodies&quot;))
+
+    (define-key map [menu-bar javascript hide-all-functions]
+      '(menu-item &quot;Hide function bodies&quot; js2-mode-toggle-hide-functions
+                  :visible (not js2-mode-functions-hidden)
+                  :help &quot;Show {...} for all top-level function bodies&quot;))
+
+    map)
+  &quot;Keymap used in `js2-mode' buffers.&quot;)
+
+(defconst js2-mode-identifier-re &quot;[a-zA-Z_$][a-zA-Z0-9_$]*&quot;)
+
+(defvar js2-mode-//-comment-re &quot;^\\(\\s-*\\)//.+&quot;
+  &quot;Matches a //-comment line.  Must be first non-whitespace on line.
+First match-group is the leading whitespace.&quot;)
+
+(defvar js2-mode-hook nil)
+
+(js2-deflocal js2-mode-ast nil &quot;Private variable.&quot;)
+(js2-deflocal js2-mode-parse-timer nil &quot;Private variable.&quot;)
+(js2-deflocal js2-mode-buffer-dirty-p nil &quot;Private variable.&quot;)
+(js2-deflocal js2-mode-parsing nil &quot;Private variable.&quot;)
+(js2-deflocal js2-mode-node-overlay nil)
+
+(defvar js2-mode-show-overlay js2-mode-dev-mode-p
+  &quot;Debug:  Non-nil to highlight AST nodes on mouse-down.&quot;)
+
+(js2-deflocal js2-mode-fontifications nil &quot;Private variable&quot;)
+(js2-deflocal js2-mode-deferred-properties nil &quot;Private variable&quot;)
+(js2-deflocal js2-imenu-recorder nil &quot;Private variable&quot;)
+(js2-deflocal js2-imenu-function-map nil &quot;Private variable&quot;)
+
+(defvar js2-paragraph-start
+  &quot;\\(@[a-zA-Z]+\\&gt;\\|$\\)&quot;)
+
+;; Note that we also set a 'c-in-sws text property in html comments,
+;; so that `c-forward-sws' and `c-backward-sws' work properly.
+(defvar js2-syntactic-ws-start
+  &quot;\\s \\|/[*/]\\|[\n\r]\\|\\\\[\n\r]\\|\\s!\\|&lt;!--\\|^\\s-*--&gt;&quot;)
+
+(defvar js2-syntactic-ws-end
+  &quot;\\s \\|[\n\r/]\\|\\s!&quot;)
+
+(defvar js2-syntactic-eol
+  (concat &quot;\\s *\\(/\\*[^*\n\r]*&quot;
+          &quot;\\(\\*+[^*\n\r/][^*\n\r]*\\)*&quot;
+          &quot;\\*+/\\s *\\)*&quot;
+          &quot;\\(//\\|/\\*[^*\n\r]*&quot;
+          &quot;\\(\\*+[^*\n\r/][^*\n\r]*\\)*$&quot;
+          &quot;\\|\\\\$\\|$\\)&quot;)
+  &quot;Copied from `java-mode'.  Needed for some cc-engine functions.&quot;)
+
+(defvar js2-comment-prefix-regexp
+  &quot;//+\\|\\**&quot;)
+
+(defvar js2-comment-start-skip
+  &quot;\\(//+\\|/\\*+\\)\\s *&quot;)
+
+(defvar js2-mode-verbose-parse-p js2-mode-dev-mode-p
+  &quot;Non-nil to emit status messages during parsing.&quot;)
+
+(defvar js2-mode-functions-hidden nil &quot;private variable&quot;)
+(defvar js2-mode-comments-hidden nil &quot;private variable&quot;)
+
+(defvar js2-mode-syntax-table
+  (let ((table (make-syntax-table)))
+    (c-populate-syntax-table table)
+    table)
+  &quot;Syntax table used in js2-mode buffers.&quot;)
+
+(defvar js2-mode-abbrev-table nil
+  &quot;Abbrev table in use in `js2-mode' buffers.&quot;)
+(define-abbrev-table 'js2-mode-abbrev-table ())
+
+(defvar js2-mode-pending-parse-callbacks nil
+  &quot;List of functions waiting to be notified that parse is finished.&quot;)
+
+(defvar js2-mode-last-indented-line -1)
+
+;;; Localizable error and warning messages
+
+;; Messages are copied from Rhino's Messages.properties.
+;; Many of the Java-specific messages have been elided.
+;; Add any js2-specific ones at the end, so we can keep
+;; this file synced with changes to Rhino's.
+
+(defvar js2-message-table
+  (make-hash-table :test 'equal :size 250)
+  &quot;Contains localized messages for js2-mode.&quot;)
+
+;; TODO(stevey):  construct this table at compile-time.
+(defmacro js2-msg (key &amp;rest strings)
+  `(puthash ,key (funcall #'concat ,@strings)
+            js2-message-table))
+
+(defun js2-get-msg (msg-key)
+  &quot;Look up a localized message.
+MSG-KEY is a list of (MSG ARGS).  If the message takes parameters,
+the correct number of ARGS must be provided.&quot;
+  (let* ((key (if (listp msg-key) (car msg-key) msg-key))
+         (args (if (listp msg-key) (cdr msg-key)))
+         (msg (gethash key js2-message-table)))
+    (if msg
+        (apply #'format msg args)
+      key)))  ; default to showing the key
+
+(js2-msg &quot;msg.dup.parms&quot;
+         &quot;Duplicate parameter name '%s'.&quot;)
+
+(js2-msg &quot;msg.too.big.jump&quot;
+         &quot;Program too complex: jump offset too big.&quot;)
+
+(js2-msg &quot;msg.too.big.index&quot;
+         &quot;Program too complex: internal index exceeds 64K limit.&quot;)
+
+(js2-msg &quot;msg.while.compiling.fn&quot;
+         &quot;Encountered code generation error while compiling function '%s': %s&quot;)
+
+(js2-msg &quot;msg.while.compiling.script&quot;
+         &quot;Encountered code generation error while compiling script: %s&quot;)
+
+;; Context
+(js2-msg &quot;msg.ctor.not.found&quot;
+         &quot;Constructor for '%s' not found.&quot;)
+
+(js2-msg &quot;msg.not.ctor&quot;
+         &quot;'%s' is not a constructor.&quot;)
+
+;; FunctionObject
+(js2-msg &quot;msg.varargs.ctor&quot;
+         &quot;Method or constructor '%s' must be static &quot;
+         &quot;with the signature (Context cx, Object[] args, &quot;
+         &quot;Function ctorObj, boolean inNewExpr) &quot;
+         &quot;to define a variable arguments constructor.&quot;)
+
+(js2-msg &quot;msg.varargs.fun&quot;
+         &quot;Method '%s' must be static with the signature &quot;
+         &quot;(Context cx, Scriptable thisObj, Object[] args, Function funObj) &quot;
+         &quot;to define a variable arguments function.&quot;)
+
+(js2-msg &quot;msg.incompat.call&quot;
+         &quot;Method '%s' called on incompatible object.&quot;)
+
+(js2-msg &quot;msg.bad.parms&quot;
+         &quot;Unsupported parameter type '%s' in method '%s'.&quot;)
+
+(js2-msg &quot;msg.bad.method.return&quot;
+         &quot;Unsupported return type '%s' in method '%s'.&quot;)
+
+(js2-msg &quot;msg.bad.ctor.return&quot;
+         &quot;Construction of objects of type '%s' is not supported.&quot;)
+
+(js2-msg &quot;msg.no.overload&quot;
+         &quot;Method '%s' occurs multiple times in class '%s'.&quot;)
+
+(js2-msg &quot;msg.method.not.found&quot;
+         &quot;Method '%s' not found in '%s'.&quot;)
+
+;; IRFactory
+
+(js2-msg &quot;msg.bad.for.in.lhs&quot;
+         &quot;Invalid left-hand side of for..in loop.&quot;)
+
+(js2-msg &quot;msg.mult.index&quot;
+         &quot;Only one variable allowed in for..in loop.&quot;)
+
+(js2-msg &quot;msg.bad.for.in.destruct&quot;
+         &quot;Left hand side of for..in loop must be an array of &quot;
+         &quot;length 2 to accept key/value pair.&quot;)
+
+(js2-msg &quot;msg.cant.convert&quot;
+         &quot;Can't convert to type '%s'.&quot;)
+
+(js2-msg &quot;msg.bad.assign.left&quot;
+         &quot;Invalid assignment left-hand side.&quot;)
+
+(js2-msg &quot;msg.bad.decr&quot;
+         &quot;Invalid decerement operand.&quot;)
+
+(js2-msg &quot;msg.bad.incr&quot;
+         &quot;Invalid increment operand.&quot;)
+
+(js2-msg &quot;msg.bad.yield&quot;
+         &quot;yield must be in a function.&quot;)
+
+(js2-msg &quot;msg.yield.parenthesized&quot;
+         &quot;yield expression must be parenthesized.&quot;)
+
+;; NativeGlobal
+(js2-msg &quot;msg.cant.call.indirect&quot;
+          &quot;Function '%s' must be called directly, and not by way of a &quot;
+          &quot;function of another name.&quot;)
+
+(js2-msg &quot;msg.eval.nonstring&quot;
+          &quot;Calling eval() with anything other than a primitive &quot;
+          &quot;string value will simply return the value. &quot;
+          &quot;Is this what you intended?&quot;)
+
+(js2-msg &quot;msg.eval.nonstring.strict&quot;
+         &quot;Calling eval() with anything other than a primitive &quot;
+         &quot;string value is not allowed in strict mode.&quot;)
+
+(js2-msg &quot;msg.bad.destruct.op&quot;
+         &quot;Invalid destructuring assignment operator&quot;)
+
+;; NativeCall
+(js2-msg &quot;msg.only.from.new&quot;
+         &quot;'%s' may only be invoked from a `new' expression.&quot;)
+
+(js2-msg &quot;msg.deprec.ctor&quot;
+         &quot;The '%s' constructor is deprecated.&quot;)
+
+;; NativeFunction
+(js2-msg &quot;msg.no.function.ref.found&quot;
+         &quot;no source found to decompile function reference %s&quot;)
+
+(js2-msg &quot;msg.arg.isnt.array&quot;
+         &quot;second argument to Function.prototype.apply must be an array&quot;)
+
+;; NativeGlobal
+(js2-msg &quot;msg.bad.esc.mask&quot;
+         &quot;invalid string escape mask&quot;)
+
+;; NativeRegExp
+(js2-msg &quot;msg.bad.quant&quot;
+  &quot;Invalid quantifier %s&quot;)
+
+(js2-msg &quot;msg.overlarge.backref&quot;
+  &quot;Overly large back reference %s&quot;)
+
+(js2-msg &quot;msg.overlarge.min&quot;
+  &quot;Overly large minimum %s&quot;)
+
+(js2-msg &quot;msg.overlarge.max&quot;
+  &quot;Overly large maximum %s&quot;)
+
+(js2-msg &quot;msg.zero.quant&quot;
+  &quot;Zero quantifier %s&quot;)
+
+(js2-msg &quot;msg.max.lt.min&quot;
+  &quot;Maximum %s less than minimum&quot;)
+
+(js2-msg &quot;msg.unterm.quant&quot;
+  &quot;Unterminated quantifier %s&quot;)
+
+(js2-msg &quot;msg.unterm.paren&quot;
+  &quot;Unterminated parenthetical %s&quot;)
+
+(js2-msg &quot;msg.unterm.class&quot;
+  &quot;Unterminated character class %s&quot;)
+
+(js2-msg &quot;msg.bad.range&quot;
+  &quot;Invalid range in character class.&quot;)
+
+(js2-msg &quot;msg.trail.backslash&quot;
+  &quot;Trailing \\ in regular expression.&quot;)
+
+(js2-msg &quot;msg.re.unmatched.right.paren&quot;
+  &quot;unmatched ) in regular expression.&quot;)
+
+(js2-msg &quot;msg.no.regexp&quot;
+  &quot;Regular expressions are not available.&quot;)
+
+(js2-msg &quot;msg.bad.backref&quot;
+  &quot;back-reference exceeds number of capturing parentheses.&quot;)
+
+(js2-msg &quot;msg.bad.regexp.compile&quot;
+         &quot;Only one argument may be specified if the first &quot;
+         &quot;argument to RegExp.prototype.compile is a RegExp object.&quot;)
+
+;; Parser
+(js2-msg &quot;msg.got.syntax.errors&quot;
+         &quot;Compilation produced %s syntax errors.&quot;)
+
+(js2-msg &quot;msg.var.redecl&quot;
+         &quot;TypeError: redeclaration of var %s.&quot;)
+
+(js2-msg &quot;msg.const.redecl&quot;
+         &quot;TypeError: redeclaration of const %s.&quot;)
+
+(js2-msg &quot;msg.let.redecl&quot;
+         &quot;TypeError: redeclaration of variable %s.&quot;)
+
+(js2-msg &quot;msg.parm.redecl&quot;
+         &quot;TypeError: redeclaration of formal parameter %s.&quot;)
+
+(js2-msg &quot;msg.fn.redecl&quot;
+         &quot;TypeError: redeclaration of function %s.&quot;)
+
+(js2-msg &quot;msg.let.decl.not.in.block&quot;
+         &quot;SyntaxError: let declaration not directly within block&quot;)
+
+;; NodeTransformer
+(js2-msg &quot;msg.dup.label&quot;
+         &quot;duplicated label&quot;)
+
+(js2-msg &quot;msg.undef.label&quot;
+         &quot;undefined label&quot;)
+
+(js2-msg &quot;msg.bad.break&quot;
+         &quot;unlabelled break must be inside loop or switch&quot;)
+
+(js2-msg &quot;msg.continue.outside&quot;
+         &quot;continue must be inside loop&quot;)
+
+(js2-msg &quot;msg.continue.nonloop&quot;
+         &quot;continue can only use labels of iteration statements&quot;)
+
+(js2-msg &quot;msg.bad.throw.eol&quot;
+         &quot;Line terminator is not allowed between the throw &quot;
+         &quot;keyword and throw expression.&quot;)
+
+(js2-msg &quot;msg.no.paren.parms&quot;
+         &quot;missing ( before function parameters.&quot;)
+
+(js2-msg &quot;msg.no.parm&quot;
+         &quot;missing formal parameter&quot;)
+
+(js2-msg &quot;msg.no.paren.after.parms&quot;
+         &quot;missing ) after formal parameters&quot;)
+
+(js2-msg &quot;msg.no.brace.body&quot;
+         &quot;missing '{' before function body&quot;)
+
+(js2-msg &quot;msg.no.brace.after.body&quot;
+         &quot;missing } after function body&quot;)
+
+(js2-msg &quot;msg.no.paren.cond&quot;
+         &quot;missing ( before condition&quot;)
+
+(js2-msg &quot;msg.no.paren.after.cond&quot;
+         &quot;missing ) after condition&quot;)
+
+(js2-msg &quot;msg.no.semi.stmt&quot;
+         &quot;missing ; before statement&quot;)
+
+(js2-msg &quot;msg.missing.semi&quot;
+         &quot;missing ; after statement&quot;)
+
+(js2-msg &quot;msg.no.name.after.dot&quot;
+         &quot;missing name after . operator&quot;)
+
+(js2-msg &quot;msg.no.name.after.coloncolon&quot;
+         &quot;missing name after :: operator&quot;)
+
+(js2-msg &quot;msg.no.name.after.dotdot&quot;
+         &quot;missing name after .. operator&quot;)
+
+(js2-msg &quot;msg.no.name.after.xmlAttr&quot;
+         &quot;missing name after .@&quot;)
+
+(js2-msg &quot;msg.no.bracket.index&quot;
+         &quot;missing ] in index expression&quot;)
+
+(js2-msg &quot;msg.no.paren.switch&quot;
+         &quot;missing ( before switch expression&quot;)
+
+(js2-msg &quot;msg.no.paren.after.switch&quot;
+         &quot;missing ) after switch expression&quot;)
+
+(js2-msg &quot;msg.no.brace.switch&quot;
+         &quot;missing '{' before switch body&quot;)
+
+(js2-msg &quot;msg.bad.switch&quot;
+         &quot;invalid switch statement&quot;)
+
+(js2-msg &quot;msg.no.colon.case&quot;
+         &quot;missing : after case expression&quot;)
+
+(js2-msg &quot;msg.double.switch.default&quot;
+         &quot;double default label in the switch statement&quot;)
+
+(js2-msg &quot;msg.no.while.do&quot;
+         &quot;missing while after do-loop body&quot;)
+
+(js2-msg &quot;msg.no.paren.for&quot;
+         &quot;missing ( after for&quot;)
+
+(js2-msg &quot;msg.no.semi.for&quot;
+         &quot;missing ; after for-loop initializer&quot;)
+
+(js2-msg &quot;msg.no.semi.for.cond&quot;
+         &quot;missing ; after for-loop condition&quot;)
+
+(js2-msg &quot;msg.in.after.for.name&quot;
+         &quot;missing in after for&quot;)
+
+(js2-msg &quot;msg.no.paren.for.ctrl&quot;
+         &quot;missing ) after for-loop control&quot;)
+
+(js2-msg &quot;msg.no.paren.with&quot;
+         &quot;missing ( before with-statement object&quot;)
+
+(js2-msg &quot;msg.no.paren.after.with&quot;
+         &quot;missing ) after with-statement object&quot;)
+
+(js2-msg &quot;msg.no.paren.after.let&quot;
+         &quot;missing ( after let&quot;)
+
+(js2-msg &quot;msg.no.paren.let&quot;
+         &quot;missing ) after variable list&quot;)
+
+(js2-msg &quot;msg.no.curly.let&quot;
+         &quot;missing } after let statement&quot;)
+
+(js2-msg &quot;msg.bad.return&quot;
+         &quot;invalid return&quot;)
+
+(js2-msg &quot;msg.no.brace.block&quot;
+         &quot;missing } in compound statement&quot;)
+
+(js2-msg &quot;msg.bad.label&quot;
+         &quot;invalid label&quot;)
+
+(js2-msg &quot;msg.bad.var&quot;
+         &quot;missing variable name&quot;)
+
+(js2-msg &quot;msg.bad.var.init&quot;
+         &quot;invalid variable initialization&quot;)
+
+(js2-msg &quot;msg.no.colon.cond&quot;
+         &quot;missing : in conditional expression&quot;)
+
+(js2-msg &quot;msg.no.paren.arg&quot;
+         &quot;missing ) after argument list&quot;)
+
+(js2-msg &quot;msg.no.bracket.arg&quot;
+         &quot;missing ] after element list&quot;)
+
+(js2-msg &quot;msg.bad.prop&quot;
+         &quot;invalid property id&quot;)
+
+(js2-msg &quot;msg.no.colon.prop&quot;
+         &quot;missing : after property id&quot;)
+
+(js2-msg &quot;msg.no.brace.prop&quot;
+         &quot;missing } after property list&quot;)
+
+(js2-msg &quot;msg.no.paren&quot;
+         &quot;missing ) in parenthetical&quot;)
+
+(js2-msg &quot;msg.reserved.id&quot;
+         &quot;identifier is a reserved word&quot;)
+
+(js2-msg &quot;msg.no.paren.catch&quot;
+         &quot;missing ( before catch-block condition&quot;)
+
+(js2-msg &quot;msg.bad.catchcond&quot;
+         &quot;invalid catch block condition&quot;)
+
+(js2-msg &quot;msg.catch.unreachable&quot;
+         &quot;any catch clauses following an unqualified catch are unreachable&quot;)
+
+(js2-msg &quot;msg.no.brace.try&quot;
+         &quot;missing '{' before try block&quot;)
+
+(js2-msg &quot;msg.no.brace.catchblock&quot;
+         &quot;missing '{' before catch-block body&quot;)
+
+(js2-msg &quot;msg.try.no.catchfinally&quot;
+         &quot;'try' without 'catch' or 'finally'&quot;)
+
+(js2-msg &quot;msg.no.return.value&quot;
+         &quot;function %s does not always return a value&quot;)
+
+(js2-msg &quot;msg.anon.no.return.value&quot;
+         &quot;anonymous function does not always return a value&quot;)
+
+(js2-msg &quot;msg.return.inconsistent&quot;
+         &quot;return statement is inconsistent with previous usage&quot;)
+
+(js2-msg &quot;msg.generator.returns&quot;
+         &quot;TypeError: generator function '%s' returns a value&quot;)
+
+(js2-msg &quot;msg.anon.generator.returns&quot;
+         &quot;TypeError: anonymous generator function returns a value&quot;)
+
+(js2-msg &quot;msg.syntax&quot;
+         &quot;syntax error&quot;)
+
+(js2-msg &quot;msg.unexpected.eof&quot;
+         &quot;Unexpected end of file&quot;)
+
+(js2-msg &quot;msg.XML.bad.form&quot;
+         &quot;illegally formed XML syntax&quot;)
+
+(js2-msg &quot;msg.XML.not.available&quot;
+         &quot;XML runtime not available&quot;)
+
+(js2-msg &quot;msg.too.deep.parser.recursion&quot;
+         &quot;Too deep recursion while parsing&quot;)
+
+(js2-msg &quot;msg.no.side.effects&quot;
+         &quot;Code has no side effects&quot;)
+
+(js2-msg &quot;msg.extra.trailing.comma&quot;
+         &quot;Trailing comma is not legal in an ECMA-262 object initializer&quot;)
+
+(js2-msg &quot;msg.array.trailing.comma&quot;
+         &quot;Trailing comma yields different behavior across browsers&quot;)
+
+(js2-msg &quot;msg.equal.as.assign&quot;
+         (concat &quot;Test for equality (==) mistyped as assignment (=)?&quot;
+                 &quot; (parenthesize to suppress warning)&quot;))
+
+(js2-msg &quot;msg.var.hides.arg&quot;
+         &quot;Variable %s hides argument&quot;)
+
+(js2-msg &quot;msg.destruct.assign.no.init&quot;
+         &quot;Missing = in destructuring declaration&quot;)
+
+;; ScriptRuntime
+(js2-msg &quot;msg.no.properties&quot;
+         &quot;%s has no properties.&quot;)
+
+(js2-msg &quot;msg.invalid.iterator&quot;
+         &quot;Invalid iterator value&quot;)
+
+(js2-msg &quot;msg.iterator.primitive&quot;
+         &quot;__iterator__ returned a primitive value&quot;)
+
+(js2-msg &quot;msg.assn.create.strict&quot;
+         &quot;Assignment to undeclared variable %s&quot;)
+
+(js2-msg &quot;msg.ref.undefined.prop&quot;
+         &quot;Reference to undefined property '%s'&quot;)
+
+(js2-msg &quot;msg.prop.not.found&quot;
+         &quot;Property %s not found.&quot;)
+
+(js2-msg &quot;msg.invalid.type&quot;
+         &quot;Invalid JavaScript value of type %s&quot;)
+
+(js2-msg &quot;msg.primitive.expected&quot;
+         &quot;Primitive type expected (had %s instead)&quot;)
+
+(js2-msg &quot;msg.namespace.expected&quot;
+         &quot;Namespace object expected to left of :: (found %s instead)&quot;)
+
+(js2-msg &quot;msg.null.to.object&quot;
+         &quot;Cannot convert null to an object.&quot;)
+
+(js2-msg &quot;msg.undef.to.object&quot;
+         &quot;Cannot convert undefined to an object.&quot;)
+
+(js2-msg &quot;msg.cyclic.value&quot;
+         &quot;Cyclic %s value not allowed.&quot;)
+
+(js2-msg &quot;msg.is.not.defined&quot;
+         &quot;'%s' is not defined.&quot;)
+
+(js2-msg &quot;msg.undef.prop.read&quot;
+         &quot;Cannot read property '%s' from %s&quot;)
+
+(js2-msg &quot;msg.undef.prop.write&quot;
+         &quot;Cannot set property '%s' of %s to '%s'&quot;)
+
+(js2-msg &quot;msg.undef.prop.delete&quot;
+         &quot;Cannot delete property '%s' of %s&quot;)
+
+(js2-msg &quot;msg.undef.method.call&quot;
+         &quot;Cannot call method '%s' of %s&quot;)
+
+(js2-msg &quot;msg.undef.with&quot;
+         &quot;Cannot apply 'with' to %s&quot;)
+
+(js2-msg &quot;msg.isnt.function&quot;
+         &quot;%s is not a function, it is %s.&quot;)
+
+(js2-msg &quot;msg.isnt.function.in&quot;
+         &quot;Cannot call property %s in object %s. &quot;
+         &quot;It is not a function, it is '%s'.&quot;)
+
+(js2-msg &quot;msg.function.not.found&quot;
+         &quot;Cannot find function %s.&quot;)
+
+(js2-msg &quot;msg.function.not.found.in&quot;
+         &quot;Cannot find function %s in object %s.&quot;)
+
+(js2-msg &quot;msg.isnt.xml.object&quot;
+         &quot;%s is not an xml object.&quot;)
+
+(js2-msg &quot;msg.no.ref.to.get&quot;
+         &quot;%s is not a reference to read reference value.&quot;)
+
+(js2-msg &quot;msg.no.ref.to.set&quot;
+         &quot;%s is not a reference to set reference value to %s.&quot;)
+
+(js2-msg &quot;msg.no.ref.from.function&quot;
+         &quot;Function %s can not be used as the left-hand &quot;
+         &quot;side of assignment or as an operand of ++ or -- operator.&quot;)
+
+(js2-msg &quot;msg.bad.default.value&quot;
+         &quot;Object's getDefaultValue() method returned an object.&quot;)
+
+(js2-msg &quot;msg.instanceof.not.object&quot;
+         &quot;Can't use instanceof on a non-object.&quot;)
+
+(js2-msg &quot;msg.instanceof.bad.prototype&quot;
+         &quot;'prototype' property of %s is not an object.&quot;)
+
+(js2-msg &quot;msg.bad.radix&quot;
+         &quot;illegal radix %s.&quot;)
+
+;; ScriptableObject
+(js2-msg &quot;msg.default.value&quot;
+         &quot;Cannot find default value for object.&quot;)
+
+(js2-msg &quot;msg.zero.arg.ctor&quot;
+         &quot;Cannot load class '%s' which has no zero-parameter constructor.&quot;)
+
+(js2-msg &quot;msg.ctor.multiple.parms&quot;
+         &quot;Can't define constructor or class %s since more than &quot;
+         &quot;one constructor has multiple parameters.&quot;)
+
+(js2-msg &quot;msg.extend.scriptable&quot;
+         &quot;%s must extend ScriptableObject in order to define property %s.&quot;)
+
+(js2-msg &quot;msg.bad.getter.parms&quot;
+         &quot;In order to define a property, getter %s must have zero &quot;
+         &quot;parameters or a single ScriptableObject parameter.&quot;)
+
+(js2-msg &quot;msg.obj.getter.parms&quot;
+         &quot;Expected static or delegated getter %s to take &quot;
+         &quot;a ScriptableObject parameter.&quot;)
+
+(js2-msg &quot;msg.getter.static&quot;
+         &quot;Getter and setter must both be static or neither be static.&quot;)
+
+(js2-msg &quot;msg.setter.return&quot;
+         &quot;Setter must have void return type: %s&quot;)
+
+(js2-msg &quot;msg.setter2.parms&quot;
+         &quot;Two-parameter setter must take a ScriptableObject as &quot;
+         &quot;its first parameter.&quot;)
+
+(js2-msg &quot;msg.setter1.parms&quot;
+         &quot;Expected single parameter setter for %s&quot;)
+
+(js2-msg &quot;msg.setter2.expected&quot;
+         &quot;Expected static or delegated setter %s to take two parameters.&quot;)
+
+(js2-msg &quot;msg.setter.parms&quot;
+         &quot;Expected either one or two parameters for setter.&quot;)
+
+(js2-msg &quot;msg.setter.bad.type&quot;
+         &quot;Unsupported parameter type '%s' in setter '%s'.&quot;)
+
+(js2-msg &quot;msg.add.sealed&quot;
+         &quot;Cannot add a property to a sealed object: %s.&quot;)
+
+(js2-msg &quot;msg.remove.sealed&quot;
+         &quot;Cannot remove a property from a sealed object: %s.&quot;)
+
+(js2-msg &quot;msg.modify.sealed&quot;
+         &quot;Cannot modify a property of a sealed object: %s.&quot;)
+
+(js2-msg &quot;msg.modify.readonly&quot;
+         &quot;Cannot modify readonly property: %s.&quot;)
+
+;; TokenStream
+(js2-msg &quot;msg.missing.exponent&quot;
+         &quot;missing exponent&quot;)
+
+(js2-msg &quot;msg.caught.nfe&quot;
+         &quot;number format error&quot;)
+
+(js2-msg &quot;msg.unterminated.string.lit&quot;
+         &quot;unterminated string literal&quot;)
+
+(js2-msg &quot;msg.unterminated.comment&quot;
+         &quot;unterminated comment&quot;)
+
+(js2-msg &quot;msg.unterminated.re.lit&quot;
+         &quot;unterminated regular expression literal&quot;)
+
+(js2-msg &quot;msg.invalid.re.flag&quot;
+         &quot;invalid flag after regular expression&quot;)
+
+(js2-msg &quot;msg.no.re.input.for&quot;
+         &quot;no input for %s&quot;)
+
+(js2-msg &quot;msg.illegal.character&quot;
+         &quot;illegal character&quot;)
+
+(js2-msg &quot;msg.invalid.escape&quot;
+         &quot;invalid Unicode escape sequence&quot;)
+
+(js2-msg &quot;msg.bad.namespace&quot;
+         &quot;not a valid default namespace statement. &quot;
+         &quot;Syntax is: default xml namespace = EXPRESSION;&quot;)
+
+;; TokensStream warnings
+(js2-msg &quot;msg.bad.octal.literal&quot;
+         &quot;illegal octal literal digit %s; &quot;
+         &quot;interpreting it as a decimal digit&quot;)
+
+(js2-msg &quot;msg.reserved.keyword&quot;
+         &quot;illegal usage of future reserved keyword %s; &quot;
+         &quot;interpreting it as ordinary identifier&quot;)
+
+(js2-msg &quot;msg.script.is.not.constructor&quot;
+         &quot;Script objects are not constructors.&quot;)
+
+;; Arrays
+(js2-msg &quot;msg.arraylength.bad&quot;
+         &quot;Inappropriate array length.&quot;)
+
+;; Arrays
+(js2-msg &quot;msg.arraylength.too.big&quot;
+         &quot;Array length %s exceeds supported capacity limit.&quot;)
+
+;; URI
+(js2-msg &quot;msg.bad.uri&quot;
+         &quot;Malformed URI sequence.&quot;)
+
+;; Number
+(js2-msg &quot;msg.bad.precision&quot;
+         &quot;Precision %s out of range.&quot;)
+
+;; NativeGenerator
+(js2-msg &quot;msg.send.newborn&quot;
+         &quot;Attempt to send value to newborn generator&quot;)
+
+(js2-msg &quot;msg.already.exec.gen&quot;
+         &quot;Already executing generator&quot;)
+
+(js2-msg &quot;msg.StopIteration.invalid&quot;
+         &quot;StopIteration may not be changed to an arbitrary object.&quot;)
+
+;; Interpreter
+(js2-msg &quot;msg.yield.closing&quot;
+         &quot;Yield from closing generator&quot;)
+
+;;; Utilities
+
+(defun js2-delete-if (predicate list)
+  &quot;Remove all items satisfying PREDICATE in LIST.&quot;
+  (loop for item in list
+        if (not (funcall predicate item))
+        collect item))
+
+(defun js2-position (element list)
+  &quot;Find 0-indexed position of ELEMENT in LIST comparing with `eq'.
+Returns nil if element is not found in the list.&quot;
+  (let ((count 0)
+        found)
+    (while (and list (not found))
+      (if (eq element (car list))
+          (setq found t)
+        (setq count (1+ count)
+              list (cdr list))))
+    (if found count)))
+
+(defun js2-find-if (predicate list)
+  &quot;Find first item satisfying PREDICATE in LIST.&quot;
+  (let (result)
+    (while (and list (not result))
+      (if (funcall predicate (car list))
+          (setq result (car list)))
+      (setq list (cdr list)))
+    result))
+
+(defmacro js2-time (form)
+  &quot;Evaluate FORM, discard result, and return elapsed time in sec&quot;
+  (declare (debug t))
+  (let ((beg (make-symbol &quot;--js2-time-beg--&quot;))
+        (delta (make-symbol &quot;--js2-time-end--&quot;)))
+    `(let ((,beg (current-time))
+           ,delta)
+       ,form
+       (/ (truncate (* (- (float-time (current-time))
+                          (float-time ,beg)))
+                    10000)
+          10000.0))))
+
+(defsubst js2-same-line (pos)
+  &quot;Return t if POS is on the same line as current point.&quot;
+  (and (&gt;= pos (point-at-bol))
+       (&lt;= pos (point-at-eol))))
+
+(defsubst js2-same-line-2 (p1 p2)
+  &quot;Return t if p1 is on the same line as p2.&quot;
+  (save-excursion
+    (goto-char p1)
+    (js2-same-line p2)))
+
+(defun js2-code-bug ()
+  &quot;Signal an error when we encounter an unexpected code path.&quot;
+  (error &quot;failed assertion&quot;))
+
+;; 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 &amp;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 &amp;optional msg-arg pos len)
+  &quot;Signal a syntax error or record a parse error.&quot;
+  (if js2-recover-from-parse-errors
+      (js2-record-parse-error msg msg-arg pos len)
+  (signal 'js2-syntax-error
+          (list msg
+                js2-ts-lineno
+                (save-excursion
+                  (goto-char js2-ts-cursor)
+                  (current-column))
+                js2-ts-hit-eof))))
+
+(defsubst js2-report-warning (msg &amp;optional msg-arg pos len)
+  (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)))
+          js2-parsed-warnings)))
+
+(defsubst js2-add-strict-warning (msg-id &amp;optional msg-arg beg end)
+  (if js2-compiler-strict-mode
+      (js2-report-warning msg-id msg-arg beg
+                          (and beg end (- end beg)))))
+
+(put 'js2-syntax-error 'error-conditions
+     '(error syntax-error js2-syntax-error))
+(put 'js2-syntax-error 'error-message &quot;Syntax error&quot;)
+
+(put 'js2-parse-error 'error-conditions
+     '(error parse-error js2-parse-error))
+(put 'js2-parse-error 'error-message &quot;Parse error&quot;)
+
+(defmacro js2-clear-flag (flags flag)
+  `(setq ,flags (logand ,flags (lognot ,flag))))
+
+(defmacro js2-set-flag (flags flag)
+  &quot;Logical-or FLAG into FLAGS.&quot;
+  `(setq ,flags (logior ,flags ,flag)))
+
+(defsubst js2-flag-set-p (flags flag)
+  (/= 0 (logand flags flag)))
+
+(defsubst js2-flag-not-set-p (flags flag)
+  (zerop (logand flags flag)))
+
+;; Stolen shamelessly from James Clark's nxml-mode.
+(defmacro js2-with-unmodifying-text-property-changes (&amp;rest body)
+  &quot;Evaluate BODY without any text property changes modifying the buffer.
+Any text properties changes happen as usual but the changes are not treated as
+modifications to the buffer.&quot;
+  (declare (indent 0) (debug t))
+  (let ((modified (make-symbol &quot;modified&quot;)))
+    `(let ((,modified (buffer-modified-p))
+	   (inhibit-read-only t)
+	   (inhibit-modification-hooks t)
+	   (buffer-undo-list t)
+	   (deactivate-mark nil)
+	   ;; Apparently these avoid file locking problems.
+	   (buffer-file-name nil)
+	   (buffer-file-truename nil))
+       (unwind-protect
+	   (progn ,@body)
+	 (unless ,modified
+	   (restore-buffer-modified-p nil))))))
+
+(defmacro js2-with-underscore-as-word-syntax (&amp;rest body)
+  &quot;Evaluate BODY with the _ character set to be word-syntax.&quot;
+  (declare (indent 0) (debug t))
+  (let ((old-syntax (make-symbol &quot;old-syntax&quot;)))
+  `(let ((,old-syntax (string (char-syntax ?_))))
+     (unwind-protect
+         (progn
+           (modify-syntax-entry ?_ &quot;w&quot; js2-mode-syntax-table)
+           ,@body)
+       (modify-syntax-entry ?_ ,old-syntax js2-mode-syntax-table)))))
+
+(defsubst js2-char-uppercase-p (c)
+  &quot;Return t if C is an uppercase character.
+Handles unicode and latin chars properly.&quot;
+  (/= c (downcase c)))
+
+(defsubst js2-char-lowercase-p (c)
+  &quot;Return t if C is an uppercase character.
+Handles unicode and latin chars properly.&quot;
+  (/= c (upcase c)))
+
+;;; AST struct and function definitions
+
+;; flags for ast node property 'member-type (used for e4x operators)
+(defvar js2-property-flag    #x1 &quot;property access: element is valid name&quot;)
+(defvar js2-attribute-flag   #x2 &quot;x.@y or x..@y&quot;)
+(defvar js2-descendants-flag #x4 &quot;x..y or x..@i&quot;)
+
+(defsubst js2-relpos (pos anchor)
+  &quot;Convert POS to be relative to ANCHOR.
+If POS is nil, returns nil.&quot;
+  (and pos (- pos anchor)))
+
+(defsubst js2-make-pad (indent)
+  (if (zerop indent)
+      &quot;&quot;
+    (make-string (* indent js2-basic-offset) ? )))
+
+(defsubst js2-visit-ast (node callback)
+  &quot;Visit every node in ast NODE with visitor CALLBACK.
+
+CALLBACK is a function that takes two arguments:  (NODE END-P).  It is
+called twice:  once to visit the node, and again after all the node's
+children have been processed.  The END-P argument is nil on the first
+call and non-nil on the second call.  The return value of the callback
+affects the traversal:  if non-nil, the children of NODE are processed.
+If the callback returns nil, or if the node has no children, then the
+callback is called immediately with a non-nil END-P argument.
+
+The node traversal is approximately lexical-order, although there
+are currently no guarantees around this.&quot;
+  (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 &quot;%s does not define a visitor-traversal function&quot;
+               (aref node 0)))))
+    ;; call the end-visit
+    (funcall callback node t)))
+
+(defstruct (js2-node
+            (:constructor nil))  ; abstract
+  &quot;Base AST node type.&quot;
+  (type -1)  ; token type
+  (pos -1)   ; start position of this AST node in parsed input
+  (len 1)    ; num characters spanned by the node
+  props      ; optional node property list (an alist)
+  parent)    ; link to parent node; null for root
+
+(defsubst js2-node-get-prop (node prop &amp;optional default)
+  (or (cadr (assoc prop (js2-node-props node))) default))
+
+(defsubst js2-node-set-prop (node prop value)
+  (setf (js2-node-props node)
+        (cons (list prop value) (js2-node-props node))))
+
+(defsubst js2-fixup-starts (n nodes)
+  &quot;Adjust the start positions of NODES to be relative to N.
+Any node in the list may be nil, for convenience.&quot;
+  (dolist (node nodes)
+    (when node
+      (setf (js2-node-pos node) (- (js2-node-pos node)
+                                   (js2-node-pos n))))))
+
+(defsubst js2-node-add-children (parent &amp;rest nodes)
+  &quot;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.&quot;
+  (js2-fixup-starts parent nodes)
+  (dolist (node nodes)
+    (and node
+         (setf (js2-node-parent node) parent))))
+
+;; Non-recursive since it's called a frightening number of times.
+(defsubst 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))))
+    pos))
+
+(defsubst js2-node-abs-end (n)
+  &quot;Return absolute buffer position of end of N.&quot;
+  (+ (js2-node-abs-pos n) (js2-node-len n)))
+
+;; 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.
+
+(defstruct (js2-block-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-block-node (&amp;key (type js2-BLOCK)
+                                                    (pos js2-token-beg)
+                                                    len
+                                                    props
+                                                    kids)))
+  &quot;A block of statements.&quot;
+  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)
+  &quot;Visit the `js2-block-node' children of AST.&quot;
+  (dolist (kid (js2-block-node-kids ast))
+    (js2-visit-ast kid callback)))
+
+(defun js2-print-block (n i)
+  (let ((pad (js2-make-pad i)))
+    (insert pad &quot;{\n&quot;)
+    (dolist (kid (js2-block-node-kids n))
+      (js2-print-ast kid (1+ i)))
+    (insert pad &quot;}&quot;)))
+
+(defstruct (js2-scope
+            (:include js2-block-node)
+            (:constructor nil)
+            (:constructor make-js2-scope (&amp;key (type js2-BLOCK)
+                                               (pos js2-token-beg)
+                                               len
+                                               kids)))
+  ;; The symbol-table is a LinkedHashMap&lt;String,Symbol&gt; in Rhino.
+  ;; I don't have one of those handy, so I'll use an alist for now.
+  ;; It's as fast as an emacs hashtable for up to about 50 elements,
+  ;; and is much lighter-weight to construct (both CPU and mem).
+  ;; The keys are interned strings (symbols) for faster lookup.
+  ;; Should switch to hybrid alist/hashtable eventually.
+  symbol-table  ; an alist of (symbol . js2-symbol)
+  parent-scope  ; a `js2-scope'
+  top)          ; top-level `js2-scope' (script/function)
+
+(put 'cl-struct-js2-scope 'js2-visitor 'js2-visit-block)
+(put 'cl-struct-js2-scope 'js2-printer 'js2-print-none)
+
+(defun js2-scope-set-parent-scope (scope parent)
+  (setf (js2-scope-parent-scope scope) parent
+        (js2-scope-top scope) (if (null parent)
+                                  scope
+                                (js2-scope-top parent))))
+
+(defun js2-node-get-enclosing-scope (node)
+  &quot;Return the innermost `js2-scope' node surrounding NODE.
+Returns nil if there is no enclosing scope node.&quot;
+  (let ((parent (js2-node-parent node)))
+    (while (not (js2-scope-p parent))
+      (setq parent (js2-node-parent parent)))
+    parent))
+
+(defun js2-get-defining-scope (scope name)
+  &quot;Search up scope chain from SCOPE looking for NAME, a string or symbol.
+Returns `js2-scope' in which NAME is defined, or nil if not found.&quot;
+  (let ((sym (if (symbolp name)
+                 name
+               (intern name)))
+        table
+        result
+        (continue t))
+    (while (and scope continue)
+      (if (and (setq table (js2-scope-symbol-table scope))
+               (assq sym table))
+          (setq continue nil
+                result scope)
+        (setq scope (js2-scope-parent-scope scope))))
+    result))
+
+(defsubst js2-scope-get-symbol (scope name)
+  &quot;Return symbol table entry for NAME in SCOPE.
+NAME can be a string or symbol.   Returns a `js2-symbol' or nil if not found.&quot;
+  (and (js2-scope-symbol-table scope)
+       (cdr (assq (if (symbolp name)
+                      name
+                    (intern name))
+                  (js2-scope-symbol-table scope)))))
+
+(defsubst js2-scope-put-symbol (scope name symbol)
+  &quot;Enter SYMBOL into symbol-table for SCOPE under NAME.
+NAME can be a lisp symbol or string.  SYMBOL is a `js2-symbol'.&quot;
+  (let* ((table (js2-scope-symbol-table scope))
+         (sym (if (symbolp name) name (intern name)))
+         (entry (assq sym table)))
+    (if entry
+        (setcdr entry symbol)
+      (push (cons sym symbol)
+            (js2-scope-symbol-table scope)))))
+
+(defstruct (js2-symbol
+            (:constructor nil)
+            (:constructor make-js2-symbol (decl-type name &amp;optional ast-node)))
+  &quot;A symbol table entry.&quot;
+  ;; One of js2-FUNCTION, js2-LP (for parameters), js2-VAR,
+  ;; js2-LET, or js2-CONST
+  decl-type
+  name  ; string
+  ast-node) ; a `js2-node'
+
+(defstruct (js2-error-node
+            (:include js2-node)
+            (:constructor nil) ; silence emacs21 byte-compiler
+            (:constructor make-js2-error-node (&amp;key (type js2-ERROR)
+                                                    (pos js2-token-beg)
+                                                    len)))
+  &quot;AST node representing a parse error.&quot;)
+
+(put 'cl-struct-js2-error-node 'js2-visitor 'js2-visit-none)
+(put 'cl-struct-js2-error-node 'js2-printer 'js2-print-none)
+
+(defstruct (js2-script-node
+            (:include js2-scope)
+            (:constructor nil)
+            (:constructor make-js2-script-node (&amp;key (type js2-SCRIPT)
+                                                     (pos js2-token-beg)
+                                                     len
+                                                     var-decls
+                                                     fun-decls)))
+  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
+  consts      ; bool-vector matching var-decls
+  (temp-number 0))  ; for generating temp variables
+
+(put 'cl-struct-js2-script-node 'js2-visitor 'js2-visit-block)
+(put 'cl-struct-js2-script-node 'js2-printer 'js2-print-script)
+
+(defun js2-print-script (node indent)
+  (dolist (kid (js2-block-node-kids node))
+    (js2-print-ast kid indent)))
+
+(defstruct (js2-ast-root
+            (:include js2-script-node)
+            (:constructor nil)
+            (:constructor make-js2-ast-root (&amp;key (type js2-SCRIPT)
+                                                  (pos js2-token-beg)
+                                                  len
+                                                  buffer)))
+  &quot;The root node of a js2 AST.&quot;
+  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
+  node-count)    ; number of nodes in the tree, including the root
+
+(put 'cl-struct-js2-ast-root 'js2-visitor 'js2-visit-ast-root)
+(put 'cl-struct-js2-ast-root 'js2-printer 'js2-print-script)
+
+(defun js2-visit-ast-root (ast callback)
+  (dolist (kid (js2-ast-root-kids ast))
+    (js2-visit-ast kid callback))
+  (dolist (comment (js2-ast-root-comments ast))
+    (js2-visit-ast comment callback)))
+
+(defstruct (js2-comment-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-comment-node (&amp;key (type js2-COMMENT)
+                                                      (pos js2-token-beg)
+                                                      len
+                                                      (format js2-ts-comment-type))))
+  format)  ; 'line, 'block, 'jsdoc or 'html
+
+(put 'cl-struct-js2-comment-node 'js2-visitor 'js2-visit-none)
+(put 'cl-struct-js2-comment-node 'js2-printer 'js2-print-comment)
+
+(defun js2-print-comment (n i)
+  ;; We really ought to link end-of-line comments to their nodes.
+  ;; Or maybe we could add a new comment type, 'endline.
+  (insert (js2-make-pad i)
+          (js2-node-string n)))
+
+(defstruct (js2-expr-stmt-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-expr-stmt-node (&amp;key (type js2-EXPR_VOID)
+                                                        (pos js2-ts-cursor)
+                                                        len
+                                                        expr)))
+  &quot;An expression statement.&quot;
+  expr)
+
+(defsubst js2-expr-stmt-node-set-has-result (node)
+  &quot;Change the node type to `js2-EXPR_RESULT'.  Used for code generation.&quot;
+  (setf (js2-node-type node) js2-EXPR_RESULT))
+
+(put 'cl-struct-js2-expr-stmt-node 'js2-visitor 'js2-visit-expr-stmt-node)
+(put 'cl-struct-js2-expr-stmt-node 'js2-printer 'js2-print-expr-stmt-node)
+
+(defun js2-visit-expr-stmt-node (n v)
+  (js2-visit-ast (js2-expr-stmt-node-expr n) v))
+
+(defun js2-print-expr-stmt-node (n indent)
+  (js2-print-ast (js2-expr-stmt-node-expr n) indent)
+  (insert &quot;;\n&quot;))
+
+(defstruct (js2-loop-node
+            (:include js2-scope)
+            (:constructor nil))
+  &quot;Abstract supertype of loop nodes.&quot;
+  body      ; a `js2-block-node'
+  lp        ; position of left-paren, nil if omitted
+  rp)       ; position of right-paren, nil if omitted
+
+(defstruct (js2-do-node
+            (:include js2-loop-node)
+            (:constructor nil)
+            (:constructor make-js2-do-node (&amp;key (type js2-DO)
+                                                 (pos js2-token-beg)
+                                                 len
+                                                 body
+                                                 condition
+                                                 while-pos
+                                                 lp
+                                                 rp)))
+  &quot;AST node for do-loop.&quot;
+  condition  ; while (expression)
+  while-pos) ; buffer position of 'while' keyword
+
+(put 'cl-struct-js2-do-node 'js2-visitor 'js2-visit-do-node)
+(put 'cl-struct-js2-do-node 'js2-printer 'js2-print-do-node)
+
+(defun js2-visit-do-node (n v)
+  (js2-visit-ast (js2-do-node-body n) v)
+  (js2-visit-ast (js2-do-node-condition n) v))
+
+(defun js2-print-do-node (n i)
+  (let ((pad (js2-make-pad i)))
+    (insert pad &quot;do {\n&quot;)
+    (dolist (kid (js2-block-node-kids (js2-do-node-body n)))
+      (js2-print-ast kid (1+ i)))
+    (insert pad &quot;} while (&quot;)
+    (js2-print-ast (js2-do-node-condition n) 0)
+    (insert &quot;);\n&quot;)))
+
+(defstruct (js2-while-node
+            (:include js2-loop-node)
+            (:constructor nil)
+            (:constructor make-js2-while-node (&amp;key (type js2-WHILE)
+                                                    (pos js2-token-beg)
+                                                    len
+                                                    body
+                                                    condition
+                                                    lp
+                                                    rp)))
+  &quot;AST node for while-loop.&quot;
+  condition)    ; while-condition
+
+(put 'cl-struct-js2-while-node 'js2-visitor 'js2-visit-while-node)
+(put 'cl-struct-js2-while-node 'js2-printer 'js2-print-while-node)
+
+(defun js2-visit-while-node (n v)
+  (js2-visit-ast (js2-while-node-condition n) v)
+  (js2-visit-ast (js2-while-node-body n) v))
+
+(defun js2-print-while-node (n i)
+  (let ((pad (js2-make-pad i)))
+    (insert pad &quot;while (&quot;)
+    (js2-print-ast (js2-while-node-condition n) 0)
+    (insert &quot;) {\n&quot;)
+    (js2-print-body (js2-while-node-body n) (1+ i))
+    (insert pad &quot;}\n&quot;)))
+
+(defstruct (js2-for-node
+            (:include js2-loop-node)
+            (:constructor nil)
+            (:constructor make-js2-for-node (&amp;key (type js2-FOR)
+                                                  (pos js2-ts-cursor)
+                                                  len
+                                                  body
+                                                  init
+                                                  condition
+                                                  update
+                                                  lp
+                                                  rp)))
+  &quot;AST node for a C-style for-loop.&quot;
+  init       ; initialization expression
+  condition  ; loop condition
+  update)    ; update clause
+
+(put 'cl-struct-js2-for-node 'js2-visitor 'js2-visit-for-node)
+(put 'cl-struct-js2-for-node 'js2-printer 'js2-print-for-node)
+
+(defun js2-visit-for-node (n v)
+  (js2-visit-ast (js2-for-node-init n) v)
+  (js2-visit-ast (js2-for-node-condition n) v)
+  (js2-visit-ast (js2-for-node-update n) v)
+  (js2-visit-ast (js2-for-node-body n) v))
+
+(defun js2-print-for-node (n i)
+  (let ((pad (js2-make-pad i)))
+    (insert pad &quot;for (&quot;)
+    (js2-print-ast (js2-for-node-init n) 0)
+    (insert &quot;; &quot;)
+    (js2-print-ast (js2-for-node-condition n) 0)
+    (insert &quot;; &quot;)
+    (js2-print-ast (js2-for-node-update n) 0)
+    (insert &quot;) {\n&quot;)
+    (js2-print-body (js2-for-node-body n) (1+ i))
+    (insert pad &quot;}\n&quot;)))
+
+(defstruct (js2-for-in-node
+            (:include js2-loop-node)
+            (:constructor nil)
+            (:constructor make-js2-for-in-node (&amp;key (type js2-FOR)
+                                                     (pos js2-ts-cursor)
+                                                     len
+                                                     body
+                                                     iterator
+                                                     object
+                                                     in-pos
+                                                     each-pos
+                                                     foreach-p
+                                                     lp
+                                                     rp)))
+  &quot;AST node for a for..in loop.&quot;
+  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
+
+(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)
+
+(defun js2-visit-for-in-node (n v)
+  (js2-visit-ast (js2-for-in-node-iterator n) v)
+  (js2-visit-ast (js2-for-in-node-object n) v)
+  (js2-visit-ast (js2-for-in-node-body n) v))
+
+(defun js2-print-for-in-node (n i)
+  (let ((pad (js2-make-pad i))
+        (foreach (js2-for-in-node-foreach-p n)))
+    (insert pad &quot;for &quot;)
+    (if foreach
+        (insert &quot;each &quot;))
+    (insert &quot;(&quot;)
+    (js2-print-ast (js2-for-in-node-iterator n) 0)
+    (insert &quot; in &quot;)
+    (js2-print-ast (js2-for-in-node-object n) 0)
+    (insert &quot;) {\n&quot;)
+    (js2-print-body (js2-for-in-node-body n) (1+ i))
+    (insert pad &quot;}\n&quot;)))
+
+(defstruct (js2-return-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-return-node (&amp;key (type js2-RETURN)
+                                                     (pos js2-ts-cursor)
+                                                     len
+                                                     retval)))
+  &quot;AST node for a return statement.&quot;
+  retval)  ; expression to return, or 'undefined
+
+(put 'cl-struct-js2-return-node 'js2-visitor 'js2-visit-return-node)
+(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)))
+
+(defun js2-print-return-node (n i)
+  (insert (js2-make-pad i) &quot;return&quot;)
+  (when (js2-return-node-retval n)
+    (insert &quot; &quot;)
+    (js2-print-ast (js2-return-node-retval n) 0))
+  (insert &quot;;\n&quot;))
+
+(defstruct (js2-if-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-if-node (&amp;key (type js2-IF)
+                                                 (pos js2-ts-cursor)
+                                                 len
+                                                 condition
+                                                 then-part
+                                                 else-pos
+                                                 else-part
+                                                 lp
+                                                 rp)))
+  &quot;AST node for an if-statement.&quot;
+  condition   ; expression
+  then-part   ; statement or block
+  else-pos    ; optional buffer position of 'else' keyword
+  else-part   ; optional statement or block
+  lp          ; position of left-paren, nil if omitted
+  rp)         ; position of right-paren, nil if omitted
+
+(put 'cl-struct-js2-if-node 'js2-visitor 'js2-visit-if-node)
+(put 'cl-struct-js2-if-node 'js2-printer 'js2-print-if-node)
+
+(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)))
+
+(defun js2-print-if-node (n i)
+  (let ((pad (js2-make-pad i))
+        (then-part (js2-if-node-then-part n))
+        (else-part (js2-if-node-else-part n)))
+    (insert pad &quot;if (&quot;)
+    (js2-print-ast (js2-if-node-condition n) 0)
+    (insert &quot;) {\n&quot;)
+    (js2-print-body then-part (1+ i))
+    (insert pad &quot;}&quot;)
+    (cond
+     ((not else-part)
+      (insert &quot;\n&quot;))
+     ((js2-if-node-p else-part)
+      (insert &quot; else &quot;)
+      (js2-print-body else-part i))
+     (t
+      (insert &quot; else {\n&quot;)
+      (js2-print-body else-part (1+ i))
+      (insert pad &quot;}\n&quot;)))))
+
+(defstruct (js2-try-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-try-node (&amp;key (type js2-TRY)
+                                                  (pos js2-ts-cursor)
+                                                  len
+                                                  try-block
+                                                  catch-clauses
+                                                  finally-block)))
+  &quot;AST node for a try-statement.&quot;
+  try-block
+  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)
+(put 'cl-struct-js2-try-node 'js2-printer 'js2-print-try-node)
+
+(defun js2-visit-try-node (n v)
+  (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)))
+
+(defun js2-print-try-node (n i)
+  (let ((pad (js2-make-pad i))
+        (catches (js2-try-node-catch-clauses n))
+        (finally (js2-try-node-finally-block n)))
+    (insert pad &quot;try {\n&quot;)
+    (js2-print-body (js2-try-node-try-block n) (1+ i))
+    (insert pad &quot;}&quot;)
+    (when catches
+      (dolist (catch catches)
+        (js2-print-ast catch i)))
+    (if finally
+        (js2-print-ast finally i)
+      (insert &quot;\n&quot;))))
+
+(defstruct (js2-catch-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-catch-node (&amp;key (type js2-CATCH)
+                                                    (pos js2-ts-cursor)
+                                                    len
+                                                    var-name
+                                                    guard-kwd
+                                                    guard-expr
+                                                    block
+                                                    lp
+                                                    rp)))
+  &quot;AST node for a catch clause.&quot;
+  var-name    ; a `js2-name-node'
+  guard-kwd   ; relative buffer position of &quot;if&quot; in &quot;catch (x if ...)&quot;
+  guard-expr  ; catch condition, a `js2-node'
+  block       ; statements, a `js2-block-node'
+  lp          ; buffer position of left-paren, nil if omitted
+  rp)         ; buffer position of right-paren, nil if omitted
+
+(put 'cl-struct-js2-catch-node 'js2-visitor 'js2-visit-catch-node)
+(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)
+  (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))
+
+(defun js2-print-catch-node (n i)
+  (let ((pad (js2-make-pad i))
+        (guard-kwd (js2-catch-node-guard-kwd n))
+        (guard-expr (js2-catch-node-guard-expr n)))
+    (insert &quot; catch (&quot;)
+    (js2-print-ast (js2-catch-node-var-name n) 0)
+    (when guard-kwd
+      (insert &quot; if &quot;)
+      (js2-print-ast guard-expr 0))
+    (insert &quot;) {\n&quot;)
+    (js2-print-body (js2-catch-node-block n) (1+ i))
+    (insert pad &quot;}&quot;)))
+
+(defstruct (js2-finally-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-finally-node (&amp;key (type js2-FINALLY)
+                                                      (pos js2-ts-cursor)
+                                                      len
+                                                      body)))
+  &quot;AST node for a finally clause.&quot;
+  body)  ; a `js2-node', often but not always a block node
+
+(put 'cl-struct-js2-finally-node 'js2-visitor 'js2-visit-finally-node)
+(put 'cl-struct-js2-finally-node 'js2-printer 'js2-print-finally-node)
+
+(defun js2-visit-finally-node (n v)
+  (js2-visit-ast (js2-finally-node-body n) v))
+
+(defun js2-print-finally-node (n i)
+  (let ((pad (js2-make-pad i)))
+    (insert &quot; finally {\n&quot;)
+    (js2-print-body (js2-finally-node-body n) (1+ i))
+    (insert pad &quot;}\n&quot;)))
+
+(defstruct (js2-switch-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-switch-node (&amp;key (type js2-SWITCH)
+                                                     (pos js2-ts-cursor)
+                                                     len
+                                                     discriminant
+                                                     cases
+                                                     lp
+                                                     rp)))
+  &quot;AST node for a switch statement.&quot;
+  discriminant  ; a `js2-node' (switch expression)
+  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
+
+(put 'cl-struct-js2-switch-node 'js2-visitor 'js2-visit-switch-node)
+(put 'cl-struct-js2-switch-node 'js2-printer 'js2-print-switch-node)
+
+(defun js2-visit-switch-node (n v)
+  (js2-visit-ast (js2-switch-node-discriminant n) v)
+  (dolist (c (js2-switch-node-cases n))
+    (js2-visit-ast c v)))
+
+(defun js2-print-switch-node (n i)
+  (let ((pad (js2-make-pad i))
+        (cases (js2-switch-node-cases n)))
+    (insert pad &quot;switch (&quot;)
+    (js2-print-ast (js2-switch-node-discriminant n) 0)
+    (insert &quot;) {\n&quot;)
+    (dolist (case cases)
+      (js2-print-ast case i))
+    (insert pad &quot;}\n&quot;)))
+
+(defstruct (js2-case-node
+            (:include js2-block-node)
+            (:constructor nil)
+            (:constructor make-js2-case-node (&amp;key (type js2-CASE)
+                                                   (pos js2-ts-cursor)
+                                                   len
+                                                   kids
+                                                   expr)))
+  &quot;AST node for a case clause of a switch statement.&quot;
+  expr)   ; the case expression (nil for default)
+
+(put 'cl-struct-js2-case-node 'js2-visitor 'js2-visit-case-node)
+(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-block n v))
+
+(defun js2-print-case-node (n i)
+  (let ((pad (js2-make-pad i))
+        (expr (js2-case-node-expr n)))
+    (insert pad)
+    (if (null expr)
+        (insert &quot;default:\n&quot;)
+      (insert &quot;case &quot;)
+      (js2-print-ast expr 0)
+      (insert &quot;:\n&quot;))
+    (dolist (kid (js2-case-node-kids n))
+      (js2-print-ast kid (1+ i)))))
+
+(defstruct (js2-throw-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-throw-node (&amp;key (type js2-THROW)
+                                                    (pos js2-ts-cursor)
+                                                    len
+                                                    expr)))
+  &quot;AST node for a throw statement.&quot;
+  expr)   ; the expression to throw
+
+(put 'cl-struct-js2-throw-node 'js2-visitor 'js2-visit-throw-node)
+(put 'cl-struct-js2-throw-node 'js2-printer 'js2-print-throw-node)
+
+(defun js2-visit-throw-node (n v)
+  (js2-visit-ast (js2-throw-node-expr n) v))
+
+(defun js2-print-throw-node (n i)
+  (insert (js2-make-pad i) &quot;throw &quot;)
+  (js2-print-ast (js2-throw-node-expr n) 0)
+  (insert &quot;;\n&quot;))
+
+(defstruct (js2-with-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-with-node (&amp;key (type js2-WITH)
+                                                   (pos js2-ts-cursor)
+                                                   len
+                                                   object
+                                                   body
+                                                   lp
+                                                   rp)))
+  &quot;AST node for a with-statement.&quot;
+  object
+  body
+  lp    ; buffer position of left-paren around object, nil if omitted
+  rp)   ; buffer position of right-paren around object, nil if omitted
+
+(put 'cl-struct-js2-with-node 'js2-visitor 'js2-visit-with-node)
+(put 'cl-struct-js2-with-node 'js2-printer 'js2-print-with-node)
+
+(defun js2-visit-with-node (n v)
+  (js2-visit-ast (js2-with-node-object n) v)
+  (js2-visit-ast (js2-with-node-body n) v))
+
+(defun js2-print-with-node (n i)
+  (let ((pad (js2-make-pad i)))
+    (insert pad &quot;with (&quot;)
+    (js2-print-ast (js2-with-node-object n) 0)
+    (insert &quot;) {\n&quot;)
+    (js2-print-body (js2-with-node-body n) (1+ i))
+    (insert pad &quot;}\n&quot;)))
+
+(defstruct (js2-label-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-label-node (&amp;key (type js2-LABEL)
+                                                    (pos js2-ts-cursor)
+                                                    len
+                                                    name)))
+  &quot;AST node for a statement label or case label.&quot;
+  name   ; a string
+  loop)  ; for validating and code-generating continue-to-label
+
+(put 'cl-struct-js2-label-node 'js2-visitor 'js2-visit-none)
+(put 'cl-struct-js2-label-node 'js2-printer 'js2-print-label)
+
+(defun js2-print-label (n i)
+  (insert (js2-make-pad i)
+          (js2-label-node-name n)
+          &quot;:\n&quot;))
+
+(defstruct (js2-labeled-stmt-node
+            (:include js2-node)
+            (:constructor nil)
+            ;; type needs to be in `js2-side-effecting-tokens' to avoid spurious
+            ;; no-side-effects warnings, hence js2-EXPR_RESULT.
+            (:constructor make-js2-labeled-stmt-node (&amp;key (type js2-EXPR_RESULT)
+                                                           (pos js2-ts-cursor)
+                                                           len
+                                                           labels
+                                                           stmt)))
+  &quot;AST node for a statement with one or more labels.
+Multiple labels for a statement are collapsed into the labels field.&quot;
+  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)
+(put 'cl-struct-js2-labeled-stmt-node 'js2-printer 'js2-print-labeled-stmt)
+
+(defun js2-get-label-by-name (lbl-stmt name)
+  &quot;Return a `js2-label-node' by NAME from LBL-STMT's labels list.
+Returns nil if no such label is in the list.&quot;
+  (let ((label-list (js2-labeled-stmt-node-labels lbl-stmt))
+        result)
+    (while (and label-list (not result))
+      (if (string= (js2-label-node-name (car label-list)) name)
+          (setq result (car label-list))
+        (setq label-list (cdr label-list))))
+    result))
+
+(defun js2-visit-labeled-stmt (n v)
+  (dolist (label (js2-labeled-stmt-node-labels n))
+    (js2-visit-ast label v))
+  (js2-visit-ast (js2-labeled-stmt-node-stmt n) v))
+
+(defun js2-print-labeled-stmt (n i)
+  (dolist (label (js2-labeled-stmt-node-labels n))
+    (js2-print-ast label i))
+  (js2-print-ast (js2-labeled-stmt-node-stmt n) (1+ i)))
+
+(defun js2-labeled-stmt-node-contains (node label)
+  &quot;Return t if NODE contains LABEL in its label set.
+NODE is a `js2-labels-node'.  LABEL is an identifier.&quot;
+  (loop for nl in (js2-labeled-stmt-node-labels node)
+        if (string= label (js2-label-node-name nl))
+          return t
+        finally return nil))
+
+(defsubst js2-labeled-stmt-node-add-label (node label)
+  &quot;Add a `js2-label-node' to the label set for this statement.&quot;
+  (setf (js2-labeled-stmt-node-labels node)
+        (nconc (js2-labeled-stmt-node-labels node) (list label))))
+
+(defstruct (js2-jump-node
+            (:include js2-node)
+            (:constructor nil))
+  &quot;Abstract supertype of break and continue nodes.&quot;
+  label   ; `js2-name-node' for location of label identifier, if present
+  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)))
+
+(defstruct (js2-break-node
+            (:include js2-jump-node)
+            (:constructor nil)
+            (:constructor make-js2-break-node (&amp;key (type js2-BREAK)
+                                                    (pos js2-ts-cursor)
+                                                    len
+                                                    label
+                                                    target)))
+  &quot;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
+is the target of the break - a label node or enclosing loop/switch statement.&quot;)
+
+(put 'cl-struct-js2-break-node 'js2-visitor 'js2-visit-jump-node)
+(put 'cl-struct-js2-break-node 'js2-printer 'js2-print-break-node)
+
+(defun js2-print-break-node (n i)
+  (insert (js2-make-pad i) &quot;break&quot;)
+  (when (js2-break-node-label n)
+    (insert &quot; &quot;)
+    (js2-print-ast (js2-break-node-label n) 0))
+  (insert &quot;;\n&quot;))
+
+(defstruct (js2-continue-node
+            (:include js2-jump-node)
+            (:constructor nil)
+            (:constructor make-js2-continue-node (&amp;key (type js2-CONTINUE)
+                                                       (pos js2-ts-cursor)
+                                                       len
+                                                       label
+                                                       target)))
+  &quot;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:
+a `js2-label-node' or the innermost enclosing loop.&quot;)
+
+(put 'cl-struct-js2-continue-node 'js2-visitor 'js2-visit-jump-node)
+(put 'cl-struct-js2-continue-node 'js2-printer 'js2-print-continue-node)
+
+(defun js2-print-continue-node (n i)
+  (insert (js2-make-pad i) &quot;continue&quot;)
+  (when (js2-continue-node-label n)
+    (insert &quot; &quot;)
+    (js2-print-ast (js2-continue-node-label n) 0))
+  (insert &quot;;\n&quot;))
+
+(defstruct (js2-function-node
+            (:include js2-script-node)
+            (:constructor nil)
+            (:constructor make-js2-function-node (&amp;key (type js2-FUNCTION)
+                                                       (pos js2-ts-cursor)
+                                                       len
+                                                       (ftype 'FUNCTION)
+                                                       (form 'FUNCTION_STATEMENT)
+                                                       (name &quot;&quot;)
+                                                       params
+                                                       body
+                                                       lp
+                                                       rp)))
+  &quot;AST node for a function declaration.
+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'.&quot;
+  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
+  body             ; a `js2-block-node' or expression node (1.8 only)
+  lp               ; position of arg-list open-paren, or nil if omitted
+  rp               ; position of arg-list close-paren, or nil if omitted
+  ignore-dynamic   ; ignore value of the dynamic-scope flag (interpreter only)
+  needs-activation ; t if we need an activation object for this frame
+  is-generator     ; t if this function contains a yield
+  member-expr)     ; nonstandard Ecma extension from Rhino
+
+(put 'cl-struct-js2-function-node 'js2-visitor 'js2-visit-function-node)
+(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))
+  (dolist (p (js2-function-node-params n))
+    (js2-visit-ast p v))
+  (when (js2-function-node-body n)
+    (js2-visit-ast (js2-function-node-body n) v)))
+
+(defun js2-print-function-node (n i)
+  (let ((pad (js2-make-pad i))
+        (getter (js2-node-get-prop n 'GETTER_SETTER))
+        (name (js2-function-node-name n))
+        (params (js2-function-node-params n))
+        (body (js2-function-node-body n))
+        (expr (eq (js2-function-node-form n) 'FUNCTION_EXPRESSION)))
+    (unless getter
+      (insert pad &quot;function&quot;))
+    (when name
+        (insert &quot; &quot;)
+        (js2-print-ast name 0))
+    (insert &quot;(&quot;)
+    (loop with len = (length params)
+          for param in params
+          for count from 1
+          do
+          (js2-print-ast param 0)
+          (if (&lt; count len)
+              (insert &quot;, &quot;)))
+    (insert &quot;) {&quot;)
+    (unless expr
+      (insert &quot;\n&quot;))
+    ;; TODO:  fix this to be smarter about indenting, etc.
+    (js2-print-body body (1+ i))
+    (insert pad &quot;}&quot;)
+    (unless expr
+      (insert &quot;\n&quot;))))
+
+(defsubst js2-function-name (node)
+  &quot;Return function name for NODE, a `js2-function-node', or nil if anonymous.&quot;
+  (and (js2-function-node-name node)
+       (js2-name-node-name (js2-function-node-name node))))
+
+;; Having this be an expression node makes it more flexible.
+;; There are IDE contexts, such as indentation in a for-loop initializer,
+;; that work better if you assume it's an expression.  Whenever we have
+;; a standalone var/const declaration, we just wrap with an expr stmt.
+;; Eclipse apparently screwed this up and now has two versions, expr and stmt.
+(defstruct (js2-var-decl-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-var-decl-node (&amp;key (type js2-VAR)
+                                                       (pos js2-token-beg)
+                                                       len
+                                                       kids
+                                                       decl-type)))
+  &quot;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.&quot;
+  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)
+(put 'cl-struct-js2-var-decl-node 'js2-printer 'js2-print-var-decl)
+
+(defun js2-visit-var-decl (n v)
+  (dolist (kid (js2-var-decl-node-kids n))
+    (js2-visit-ast kid v)))
+
+(defun js2-print-var-decl (n i)
+  (let ((pad (js2-make-pad i))
+        (tt (js2-var-decl-node-decl-type n)))
+    (insert pad)
+    (insert (cond
+             ((= tt js2-VAR) &quot;var &quot;)
+             ((= tt js2-LET) &quot;&quot;)  ; handled by parent let-{expr/stmt}
+             ((= tt js2-CONST) &quot;const &quot;)
+             (t
+              (error &quot;malformed var-decl node&quot;))))
+    (loop with kids = (js2-var-decl-node-kids n)
+          with len = (length kids)
+          for kid in kids
+          for count from 1
+          do
+          (js2-print-ast kid 0)
+          (if (&lt; count len)
+              (insert &quot;, &quot;)))))
+
+(defstruct (js2-var-init-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-var-init-node (&amp;key (type js2-VAR)
+                                                       (pos js2-ts-cursor)
+                                                       len
+                                                       target
+                                                       initializer)))
+  &quot;AST node for a variable declaration.
+The type field will be js2-CONST for a const decl.&quot;
+  target        ; `js2-name-node', `js2-object-node', or `js2-array-node'
+  initializer)  ; initializer expression, a `js2-node'
+
+(put 'cl-struct-js2-var-init-node 'js2-visitor 'js2-visit-var-init-node)
+(put 'cl-struct-js2-var-init-node 'js2-printer 'js2-print-var-init-node)
+
+(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)))
+
+(defun js2-print-var-init-node (n i)
+  (let ((pad (js2-make-pad i))
+        (name (js2-var-init-node-target n))
+        (init (js2-var-init-node-initializer n)))
+    (insert pad)
+    (js2-print-ast name 0)
+    (when init
+      (insert &quot; = &quot;)
+      (js2-print-ast init 0))))
+
+(defstruct (js2-cond-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-cond-node (&amp;key (type js2-HOOK)
+                                                   (pos js2-ts-cursor)
+                                                   len
+                                                   test-expr
+                                                   true-expr
+                                                   false-expr
+                                                   q-pos
+                                                   c-pos)))
+  &quot;AST node for the ternary operator&quot;
+  test-expr
+  true-expr
+  false-expr
+  q-pos   ; buffer position of ?
+  c-pos)  ; buffer position of :
+
+(put 'cl-struct-js2-cond-node 'js2-visitor 'js2-visit-cond-node)
+(put 'cl-struct-js2-cond-node 'js2-printer 'js2-print-cond-node)
+
+(defun js2-visit-cond-node (n v)
+  (js2-visit-ast (js2-cond-node-test-expr n) v)
+  (js2-visit-ast (js2-cond-node-true-expr n) v)
+  (js2-visit-ast (js2-cond-node-false-expr n) v))
+
+(defun js2-print-cond-node (n i)
+  (let ((pad (js2-make-pad i)))
+    (insert pad)
+    (js2-print-ast (js2-cond-node-test-expr n) 0)
+    (insert &quot; ? &quot;)
+    (js2-print-ast (js2-cond-node-true-expr n) 0)
+    (insert &quot; : &quot;)
+    (js2-print-ast (js2-cond-node-false-expr n) 0)))
+
+(defstruct (js2-infix-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-infix-node (&amp;key type
+                                                    (pos js2-ts-cursor)
+                                                    len
+                                                    op-pos
+                                                    left
+                                                    right)))
+  &quot;Represents infix expressions.
+Includes assignment ops like `|=', and the comma operator.
+The type field inherited from `js2-node' holds the operator.&quot;
+  op-pos    ; buffer position where operator begins
+  left      ; any `js2-node'
+  right)    ; any `js2-node'
+
+(put 'cl-struct-js2-infix-node 'js2-visitor 'js2-visit-infix-node)
+(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)))
+
+(defconst js2-operator-tokens
+  (let ((table (make-hash-table :test 'eq))
+        (tokens
+         (list (cons js2-IN &quot;in&quot;)
+               (cons js2-TYPEOF &quot;typeof&quot;)
+               (cons js2-INSTANCEOF &quot;instanceof&quot;)
+               (cons js2-DELPROP &quot;delete&quot;)
+               (cons js2-COMMA &quot;,&quot;)
+               (cons js2-COLON &quot;:&quot;)
+               (cons js2-OR &quot;||&quot;)
+               (cons js2-AND &quot;&amp;&amp;&quot;)
+               (cons js2-INC &quot;++&quot;)
+               (cons js2-DEC &quot;--&quot;)
+               (cons js2-BITOR &quot;|&quot;)
+               (cons js2-BITXOR &quot;^&quot;)
+               (cons js2-BITAND &quot;&amp;&quot;)
+               (cons js2-EQ &quot;==&quot;)
+               (cons js2-NE &quot;!=&quot;)
+               (cons js2-LT &quot;&lt;&quot;)
+               (cons js2-LE &quot;&lt;=&quot;)
+               (cons js2-GT &quot;&gt;&quot;)
+               (cons js2-GE &quot;&gt;=&quot;)
+               (cons js2-LSH &quot;&lt;&lt;&quot;)
+               (cons js2-RSH &quot;&gt;&gt;&quot;)
+               (cons js2-URSH &quot;&gt;&gt;&gt;&quot;)
+               (cons js2-ADD &quot;+&quot;)       ; infix plus
+               (cons js2-SUB &quot;-&quot;)       ; infix minus
+               (cons js2-MUL &quot;*&quot;)
+               (cons js2-DIV &quot;/&quot;)
+               (cons js2-MOD &quot;%&quot;)
+               (cons js2-NOT &quot;!&quot;)
+               (cons js2-BITNOT &quot;~&quot;)
+               (cons js2-POS &quot;+&quot;)       ; unary plus
+               (cons js2-NEG &quot;-&quot;)       ; unary minus
+               (cons js2-SHEQ &quot;===&quot;)    ; shallow equality
+               (cons js2-SHNE &quot;!==&quot;)    ; shallow inequality
+               (cons js2-ASSIGN &quot;=&quot;)
+               (cons js2-ASSIGN_BITOR &quot;|=&quot;)
+               (cons js2-ASSIGN_BITXOR &quot;^=&quot;)
+               (cons js2-ASSIGN_BITAND &quot;&amp;=&quot;)
+               (cons js2-ASSIGN_LSH &quot;&lt;&lt;=&quot;)
+               (cons js2-ASSIGN_RSH &quot;&gt;&gt;=&quot;)
+               (cons js2-ASSIGN_URSH &quot;&gt;&gt;&gt;=&quot;)
+               (cons js2-ASSIGN_ADD &quot;+=&quot;)
+               (cons js2-ASSIGN_SUB &quot;-=&quot;)
+               (cons js2-ASSIGN_MUL &quot;*=&quot;)
+               (cons js2-ASSIGN_DIV &quot;/=&quot;)
+               (cons js2-ASSIGN_MOD &quot;%=&quot;))))
+    (loop for (k . v) in tokens do
+          (puthash k v table))
+    table))
+
+(defun js2-print-infix-node (n i)
+  (let* ((tt (js2-node-type n))
+         (op (gethash tt js2-operator-tokens)))
+    (unless op
+      (error &quot;unrecognized infix operator %s&quot; (js2-node-type n)))
+    (insert (js2-make-pad i))
+    (js2-print-ast (js2-infix-node-left n) 0)
+    (unless (= tt js2-COMMA)
+      (insert &quot; &quot;))
+    (insert op)
+    (insert &quot; &quot;)
+    (js2-print-ast (js2-infix-node-right n) 0)))
+
+(defstruct (js2-assign-node
+            (:include js2-infix-node)
+            (:constructor nil)
+            (:constructor make-js2-assign-node (&amp;key type
+                                                     (pos js2-ts-cursor)
+                                                     len
+                                                     op-pos
+                                                     left
+                                                     right)))
+  &quot;Represents any assignment.
+The type field holds the actual assignment operator.&quot;)
+
+(put 'cl-struct-js2-assign-node 'js2-visitor 'js2-visit-infix-node)
+(put 'cl-struct-js2-assign-node 'js2-printer 'js2-print-infix-node)
+
+(defstruct (js2-unary-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-unary-node (&amp;key type ; required
+                                                    (pos js2-ts-cursor)
+                                                    len
+                                                    operand)))
+  &quot;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
+property is added if the operator follows the operand.&quot;
+  operand)  ; a `js2-node' expression
+
+(put 'cl-struct-js2-unary-node 'js2-visitor 'js2-visit-unary-node)
+(put 'cl-struct-js2-unary-node 'js2-printer 'js2-print-unary-node)
+
+(defun js2-visit-unary-node (n v)
+  (js2-visit-ast (js2-unary-node-operand n) v))
+
+(defun js2-print-unary-node (n i)
+  (let* ((tt (js2-node-type n))
+         (op (gethash tt js2-operator-tokens))
+         (postfix (js2-node-get-prop n 'postfix)))
+    (unless op
+      (error &quot;unrecognized unary operator %s&quot; tt))
+    (insert (js2-make-pad i))
+    (unless postfix
+      (insert op))
+    (if (or (= tt js2-TYPEOF)
+            (= tt js2-DELPROP))
+        (insert &quot; &quot;))
+    (js2-print-ast (js2-unary-node-operand n) 0)
+    (when postfix
+      (insert op))))
+
+(defstruct (js2-let-node
+            (:include js2-scope)
+            (:constructor nil)
+            (:constructor make-js2-let-node (&amp;key (type js2-LETEXPR)
+                                                  (pos js2-token-beg)
+                                                  len
+                                                  vars
+                                                  body
+                                                  lp
+                                                  rp)))
+  &quot;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'.&quot;
+  vars   ; a `js2-var-decl-node'
+  body   ; a `js2-node' representing the expression or body block
+  lp
+  rp)
+
+(put 'cl-struct-js2-let-node 'js2-visitor 'js2-visit-let-node)
+(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)))
+
+(defun js2-print-let-node (n i)
+  (insert (js2-make-pad i) &quot;let (&quot;)
+  (js2-print-ast (js2-let-node-vars n) 0)
+  (insert &quot;) &quot;)
+  (js2-print-ast (js2-let-node-body n) i))
+
+(defstruct (js2-keyword-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-keyword-node (&amp;key type
+                                                      (pos js2-token-beg)
+                                                      (len (- js2-ts-cursor pos)))))
+  &quot;AST node representing a literal keyword such as `null'.
+Used for `null', `this', `true', `false' and `debugger'.
+The node type is set to js2-NULL, js2-THIS, etc.&quot;)
+
+(put 'cl-struct-js2-keyword-node 'js2-visitor 'js2-visit-none)
+(put 'cl-struct-js2-keyword-node 'js2-printer 'js2-print-keyword-node)
+
+(defun js2-print-keyword-node (n i)
+  (insert (js2-make-pad i)
+          (let ((tt (js2-node-type n)))
+            (cond
+             ((= tt js2-THIS) &quot;this&quot;)
+             ((= tt js2-NULL) &quot;null&quot;)
+             ((= tt js2-TRUE) &quot;true&quot;)
+             ((= tt js2-FALSE) &quot;false&quot;)
+             ((= tt js2-DEBUGGER) &quot;debugger&quot;)
+             (t (error &quot;Invalid keyword literal type: %d&quot; tt))))))
+
+(defsubst js2-this-node-p (node)
+  &quot;Return t if this node is a `js2-literal-node' of type js2-THIS.&quot;
+  (eq (js2-node-type node) js2-THIS))
+
+(defstruct (js2-new-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-new-node (&amp;key (type js2-NEW)
+                                                  (pos js2-token-beg)
+                                                  len
+                                                  target
+                                                  args
+                                                  initializer
+                                                  lp
+                                                  rp)))
+  &quot;AST node for new-expression such as new Foo().&quot;
+  target  ; an identifier or reference
+  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'
+
+(put 'cl-struct-js2-new-node 'js2-visitor 'js2-visit-new-node)
+(put 'cl-struct-js2-new-node 'js2-printer 'js2-print-new-node)
+
+(defun js2-visit-new-node (n v)
+  (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)))
+
+(defun js2-print-new-node (n i)
+  (insert (js2-make-pad i) &quot;new &quot;)
+  (js2-print-ast (js2-new-node-target n))
+  (insert &quot;(&quot;)
+  (js2-print-list (js2-new-node-args n))
+  (insert &quot;)&quot;)
+  (when (js2-new-node-initializer n)
+    (insert &quot; &quot;)
+    (js2-print-ast (js2-new-node-initializer n))))
+
+(defstruct (js2-name-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-name-node (&amp;key (type js2-NAME)
+                                                   (pos js2-token-beg)
+                                                   (len (- js2-ts-cursor
+                                                           js2-token-beg))
+                                                   (name js2-ts-string))))
+  &quot;AST node for a JavaScript identifier&quot;
+  name   ; a string
+  scope) ; a `js2-scope' (optional, used for codegen)
+
+(put 'cl-struct-js2-name-node 'js2-visitor 'js2-visit-none)
+(put 'cl-struct-js2-name-node 'js2-printer 'js2-print-name-node)
+
+(defun js2-print-name-node (n i)
+  (insert (js2-make-pad i)
+          (js2-name-node-name n)))
+
+(defsubst js2-name-node-length (node)
+  &quot;Return identifier length of NODE, a `js2-name-node'.
+Returns 0 if NODE is nil or its identifier field is nil.&quot;
+  (if node
+      (length (js2-name-node-name node))
+    0))
+
+(defstruct (js2-number-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-number-node (&amp;key (type js2-NUMBER)
+                                                     (pos js2-token-beg)
+                                                     (len (- js2-ts-cursor
+                                                             js2-token-beg))
+                                                     (value js2-ts-string)
+                                                     (num-value js2-ts-number))))
+  &quot;AST node for a number literal.&quot;
+  value      ; the original string, e.g. &quot;6.02e23&quot;
+  num-value) ; the parsed number value
+
+(put 'cl-struct-js2-number-node 'js2-visitor 'js2-visit-none)
+(put 'cl-struct-js2-number-node 'js2-printer 'js2-print-number-node)
+
+(defun js2-print-number-node (n i)
+  (insert (js2-make-pad i)
+          (number-to-string (js2-number-node-num-value n))))
+
+(defstruct (js2-regexp-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-regexp-node (&amp;key (type js2-REGEXP)
+                                                     (pos js2-token-beg)
+                                                     (len (- js2-ts-cursor
+                                                             js2-token-beg))
+                                                     value
+                                                     flags)))
+  &quot;AST node for a regular expression literal.&quot;
+  value  ; the regexp string, without // delimiters
+  flags) ; a string of flags, e.g. `mi'.
+
+(put 'cl-struct-js2-regexp-node 'js2-visitor 'js2-visit-none)
+(put 'cl-struct-js2-regexp-node 'js2-printer 'js2-print-regexp)
+
+(defun js2-print-regexp (n i)
+  (insert (js2-make-pad i)
+          &quot;/&quot;
+          (js2-regexp-node-value n)
+          &quot;/&quot;)
+  (if (js2-regexp-node-flags n)
+      (insert (js2-regexp-node-flags n))))
+
+(defstruct (js2-string-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-string-node (&amp;key (type js2-STRING)
+                                                     (pos js2-token-beg)
+                                                     (len (- js2-ts-cursor
+                                                             js2-token-beg))
+                                                     (value js2-ts-string))))
+  &quot;String literal.
+Escape characters are not evaluated; e.g. \n is 2 chars in value field.
+You can tell the quote type by looking at the first character.&quot;
+  value) ; the characters of the string, including the quotes
+
+(put 'cl-struct-js2-string-node 'js2-visitor 'js2-visit-none)
+(put 'cl-struct-js2-string-node 'js2-printer 'js2-print-string-node)
+
+(defun js2-print-string-node (n i)
+  (insert (js2-make-pad i)
+          (js2-node-string n)))
+
+(defstruct (js2-array-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-array-node (&amp;key (type js2-ARRAYLIT)
+                                                    (pos js2-ts-cursor)
+                                                    len
+                                                    elems)))
+  &quot;AST node for an array literal.&quot;
+  elems)  ; list of expressions.  [foo,,bar] yields a nil middle element.
+
+(put 'cl-struct-js2-array-node 'js2-visitor 'js2-visit-array-node)
+(put 'cl-struct-js2-array-node 'js2-printer 'js2-print-array-node)
+
+(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))))
+
+(defun js2-print-array-node (n i)
+  (insert (js2-make-pad i) &quot;[&quot;)
+  (js2-print-list (js2-array-node-elems n))
+  (insert &quot;]&quot;))
+
+(defstruct (js2-object-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-object-node (&amp;key (type js2-OBJECTLIT)
+                                                     (pos js2-ts-cursor)
+                                                     len
+                                                     elems)))
+  &quot;AST node for an object literal expression.&quot;
+  elems)  ; a lisp list of `js2-object-prop-node'
+
+(put 'cl-struct-js2-object-node 'js2-visitor 'js2-visit-object-node)
+(put 'cl-struct-js2-object-node 'js2-printer 'js2-print-object-node)
+
+(defun js2-visit-object-node (n v)
+  (dolist (e (js2-object-node-elems n))
+    (js2-visit-ast e v)))
+
+(defun js2-print-object-node (n i)
+  (insert (js2-make-pad i) &quot;{&quot;)
+  (js2-print-list (js2-object-node-elems n))
+  (insert &quot;}&quot;))
+
+(defstruct (js2-object-prop-node
+            (:include js2-infix-node)
+            (:constructor nil)
+            (:constructor make-js2-object-prop-node (&amp;key (type js2-COLON)
+                                                          (pos js2-ts-cursor)
+                                                          len
+                                                          left
+                                                          right
+                                                          op-pos)))
+  &quot;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.&quot;)
+
+(put 'cl-struct-js2-object-prop-node 'js2-visitor 'js2-visit-infix-node)
+(put 'cl-struct-js2-object-prop-node 'js2-printer 'js2-print-object-prop-node)
+
+(defun js2-print-object-prop-node (n i)
+  (insert (js2-make-pad i))
+  (js2-print-ast (js2-object-prop-node-left n) 0)
+  (insert &quot;:&quot;)
+  (js2-print-ast (js2-object-prop-node-right n) 0))
+
+(defstruct (js2-getter-setter-node
+            (:include js2-infix-node)
+            (:constructor nil)
+            (:constructor make-js2-getter-setter-node (&amp;key type ; GET or SET
+                                                            (pos js2-ts-cursor)
+                                                            len
+                                                            left
+                                                            right)))
+  &quot;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
+property `GETTER_SETTER' set to js2-GET or js2-SET. &quot;)
+
+(put 'cl-struct-js2-getter-setter-node 'js2-visitor 'js2-visit-infix-node)
+(put 'cl-struct-js2-getter-setter-node 'js2-printer 'js2-print-getter-setter)
+
+(defun js2-print-getter-setter (n i)
+  (let ((pad (js2-make-pad i))
+        (left (js2-getter-setter-node-left n))
+        (right (js2-getter-setter-node-right n)))
+    (insert pad)
+    (insert (if (= (js2-node-type n) js2-GET) &quot;get &quot; &quot;set &quot;))
+    (js2-print-ast left 0)
+    (js2-print-ast right 0)))
+
+(defstruct (js2-prop-get-node
+            (:include js2-infix-node)
+            (:constructor nil)
+            (:constructor make-js2-prop-get-node (&amp;key (type js2-GETPROP)
+                                                       (pos js2-ts-cursor)
+                                                       len
+                                                       left
+                                                       right)))
+  &quot;AST node for a dotted property reference, e.g. foo.bar or foo().bar&quot;)
+
+(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)))
+
+(defun js2-print-prop-get-node (n i)
+  (insert (js2-make-pad i))
+  (js2-print-ast (js2-prop-get-node-left n) 0)
+  (insert &quot;.&quot;)
+  (js2-print-ast (js2-prop-get-node-right n) 0))
+
+(defstruct (js2-elem-get-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-elem-get-node (&amp;key (type js2-GETELEM)
+                                                       (pos js2-ts-cursor)
+                                                       len
+                                                       target
+                                                       element
+                                                       lb
+                                                       rb)))
+  &quot;AST node for an array index expression such as foo[bar].&quot;
+  target  ; a `js2-node' - the expression preceding the &quot;.&quot;
+  element ; a `js2-node' - the expression in brackets
+  lb      ; position of left-bracket, nil if omitted
+  rb)     ; position of right-bracket, nil if omitted
+
+(put 'cl-struct-js2-elem-get-node 'js2-visitor 'js2-visit-elem-get-node)
+(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)))
+
+(defun js2-print-elem-get-node (n i)
+  (insert (js2-make-pad i))
+  (js2-print-ast (js2-elem-get-node-target n) 0)
+  (insert &quot;[&quot;)
+  (js2-print-ast (js2-elem-get-node-element n) 0)
+  (insert &quot;]&quot;))
+
+(defstruct (js2-call-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-call-node (&amp;key (type js2-CALL)
+                                                   (pos js2-ts-cursor)
+                                                   len
+                                                   target
+                                                   args
+                                                   lp
+                                                   rp)))
+  &quot;AST node for a JavaScript function call.&quot;
+  target  ; a `js2-node' evaluating to the function to call
+  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
+
+(put 'cl-struct-js2-call-node 'js2-visitor 'js2-visit-call-node)
+(put 'cl-struct-js2-call-node 'js2-printer 'js2-print-call-node)
+
+(defun js2-visit-call-node (n v)
+  (js2-visit-ast (js2-call-node-target n) v)
+  (dolist (arg (js2-call-node-args n))
+    (js2-visit-ast arg v)))
+
+(defun js2-print-call-node (n i)
+  (insert (js2-make-pad i))
+  (js2-print-ast (js2-call-node-target n) 0)
+  (insert &quot;(&quot;)
+  (js2-print-list (js2-call-node-args n))
+  (insert &quot;)&quot;))
+
+(defstruct (js2-yield-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-yield-node (&amp;key (type js2-YIELD)
+                                                    (pos js2-ts-cursor)
+                                                    len
+                                                    value)))
+  &quot;AST node for yield statement or expression.&quot;
+  value) ; optional:  value to be yielded
+
+(put 'cl-struct-js2-yield-node 'js2-visitor 'js2-visit-yield-node)
+(put 'cl-struct-js2-yield-node 'js2-printer 'js2-print-yield-node)
+
+(defun js2-visit-yield-node (n v)
+  (js2-visit-ast (js2-yield-node-value n) v))
+
+(defun js2-print-yield-node (n i)
+  (insert (js2-make-pad i))
+  (insert &quot;yield&quot;)
+  (when (js2-yield-node-value n)
+    (insert &quot; &quot;)
+    (js2-print-ast (js2-yield-node-value n) 0)))
+
+(defstruct (js2-paren-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-paren-node (&amp;key (type js2-LP)
+                                                    (pos js2-ts-cursor)
+                                                    len
+                                                    expr)))
+  &quot;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.&quot;
+  expr)   ; `js2-node'
+
+(put 'cl-struct-js2-paren-node 'js2-visitor 'js2-visit-paren-node)
+(put 'cl-struct-js2-paren-node 'js2-printer 'js2-print-paren-node)
+
+(defun js2-visit-paren-node (n v)
+  (js2-visit-ast (js2-paren-node-expr n) v))
+
+(defun js2-print-paren-node (n i)
+  (insert (js2-make-pad i))
+  (insert &quot;(&quot;)
+  (js2-print-ast (js2-paren-node-expr n) 0)
+  (insert &quot;)&quot;))
+
+(defstruct (js2-array-comp-node
+            (:include js2-scope)
+            (:constructor nil)
+            (:constructor make-js2-array-comp-node (&amp;key (type js2-ARRAYCOMP)
+                                                         (pos js2-ts-cursor)
+                                                         len
+                                                         result
+                                                         loops
+                                                         filter
+                                                         if-pos
+                                                         lp
+                                                         rp)))
+  &quot;AST node for an Array comprehension such as [[x,y] for (x in foo) for (y in bar)].&quot;
+  result  ; result expression (just after left-bracket)
+  loops   ; a lisp list of `js2-array-comp-loop-node'
+  filter  ; guard/filter expression
+  if-pos  ; buffer pos of 'if' keyword, if present, else nil
+  lp      ; buffer position of if-guard left-paren, or nil if not present
+  rp)     ; buffer position of if-guard right-paren, or nil if not present
+
+(put 'cl-struct-js2-array-comp-node 'js2-visitor 'js2-visit-array-comp-node)
+(put 'cl-struct-js2-array-comp-node 'js2-printer 'js2-print-array-comp-node)
+
+(defun js2-visit-array-comp-node (n v)
+  (js2-visit-ast (js2-array-comp-node-result n) v)
+  (dolist (l (js2-array-comp-node-loops n))
+    (js2-visit-ast l v))
+  (if (js2-array-comp-node-filter n)
+      (js2-visit-ast (js2-array-comp-node-filter n) v)))
+
+(defun js2-print-array-comp-node (n i)
+  (let ((pad (js2-make-pad i))
+        (result (js2-array-comp-node-result n))
+        (loops (js2-array-comp-node-loops n))
+        (filter (js2-array-comp-node-filter n)))
+    (insert pad &quot;[&quot;)
+    (js2-print-ast result 0)
+    (dolist (l loops)
+      (insert &quot; &quot;)
+      (js2-print-ast l 0))
+    (when filter
+      (insert &quot; if (&quot;)
+      (js2-print-ast filter 0))
+    (insert &quot;)]&quot;)))
+
+(defstruct (js2-array-comp-loop-node
+            (:include js2-for-in-node)
+            (:constructor nil)
+            (:constructor make-js2-array-comp-loop-node (&amp;key (type js2-FOR)
+                                                              (pos js2-ts-cursor)
+                                                              len
+                                                              iterator
+                                                              object
+                                                              in-pos
+                                                              foreach-p
+                                                              each-pos
+                                                              lp
+                                                              rp)))
+  &quot;AST subtree for each 'for (foo in bar)' loop in an array comprehension.&quot;)
+
+(put 'cl-struct-js2-array-comp-loop-node 'js2-visitor 'js2-visit-array-comp-loop)
+(put 'cl-struct-js2-array-comp-loop-node 'js2-printer 'js2-print-array-comp-loop)
+
+(defun js2-visit-array-comp-loop (n v)
+  (js2-visit-ast (js2-array-comp-loop-node-iterator n) v)
+  (js2-visit-ast (js2-array-comp-loop-node-object n) v))
+
+(defun js2-print-array-comp-loop (n i)
+  (insert &quot;for (&quot;)
+  (js2-print-ast (js2-array-comp-loop-node-iterator n) 0)
+  (insert &quot; in &quot;)
+  (js2-print-ast (js2-array-comp-loop-node-object n) 0)
+  (insert &quot;)&quot;))
+
+(defstruct (js2-empty-expr-node
+            (:include js2-node)
+            (:constructor nil)
+            (:constructor make-js2-empty-expr-node (&amp;key (type js2-EMPTY)
+                                                         (pos js2-token-beg)
+                                                         len)))
+  &quot;AST node for an empty expression.&quot;)
+
+(put 'cl-struct-js2-empty-expr-node 'js2-visitor 'js2-visit-none)
+(put 'cl-struct-js2-empty-expr-node 'js2-printer 'js2-print-none)
+
+(defstruct (js2-xml-node
+            (:include js2-block-node)
+            (:constructor nil)
+            (:constructor make-js2-xml-node (&amp;key (type js2-XML)
+                                                  (pos js2-token-beg)
+                                                  len
+                                                  kids)))
+  &quot;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.&quot;)
+
+(put 'cl-struct-js2-xml-node 'js2-visitor 'js2-visit-block)
+(put 'cl-struct-js2-xml-node 'js2-printer 'js2-print-xml-node)
+
+(defun js2-print-xml-node (n i)
+  (dolist (kid (js2-xml-node-kids n))
+    (js2-print-ast kid i)))
+
+(defstruct (js2-xml-js-expr-node
+            (:include js2-xml-node)
+            (:constructor nil)
+            (:constructor make-js2-xml-js-expr-node (&amp;key (type js2-XML)
+                                                          (pos js2-ts-cursor)
+                                                          len
+                                                          expr)))
+  &quot;AST node for an embedded JavaScript {expression} in an E4X literal.
+The start and end fields correspond to the curly-braces.&quot;
+  expr)  ; a `js2-expr-node' of some sort
+
+(put 'cl-struct-js2-xml-js-expr-node 'js2-visitor 'js2-visit-xml-js-expr)
+(put 'cl-struct-js2-xml-js-expr-node 'js2-printer 'js2-print-xml-js-expr)
+
+(defun js2-visit-xml-js-expr (n v)
+  (js2-visit-ast (js2-xml-js-expr-node-expr n) v))
+
+(defun js2-print-xml-js-expr (n i)
+  (insert (js2-make-pad i))
+  (insert &quot;{&quot;)
+  (js2-print-ast (js2-xml-js-expr-node-expr n) 0)
+  (insert &quot;}&quot;))
+
+(defstruct (js2-xml-dot-query-node
+            (:include js2-infix-node)
+            (:constructor nil)
+            (:constructor make-js2-xml-dot-query-node (&amp;key (type js2-DOTQUERY)
+                                                            (pos js2-ts-cursor)
+                                                            op-pos
+                                                            len
+                                                            left
+                                                            right
+                                                            rp)))
+  &quot;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
+between the dot and the lp by the scanner.&quot;
+  rp)
+
+(put 'cl-struct-js2-xml-dot-query-node 'js2-visitor 'js2-visit-infix-node)
+(put 'cl-struct-js2-xml-dot-query-node 'js2-printer 'js2-print-xml-dot-query)
+
+(defun js2-print-xml-dot-query (n i)
+  (insert (js2-make-pad i))
+  (js2-print-ast (js2-xml-dot-query-node-left n) 0)
+  (insert &quot;.(&quot;)
+  (js2-print-ast (js2-xml-dot-query-node-right n) 0)
+  (insert &quot;)&quot;))
+
+(defstruct (js2-xml-ref-node
+            (:include js2-node)
+            (:constructor nil))  ; abstract
+  &quot;Base type for E4X XML attribute-access or property-get expressions.
+Such expressions can take a variety of forms.  The general syntax has
+three parts:
+
+  - (optional) an @ (specifying an attribute access)
+  - (optional) a namespace (a `js2-name-node') and double-colon
+  - (required) either a `js2-name-node' or a bracketed [expression]
+
+The property-name expressions (examples:  ns::name, @name) are
+represented as `js2-xml-prop-ref' nodes.  The bracketed-expression
+versions (examples:  ns::[name], @[name]) become `js2-xml-elem-ref' nodes.
+
+This node type (or more specifically, its subclasses) will sometimes
+be the right-hand child of a `js2-prop-get-node' or a
+`js2-infix-node' of type `js2-DOTDOT', the .. xml-descendants operator.
+The `js2-xml-ref-node' may also be a standalone primary expression with
+no explicit target, which is valid in certain expression contexts such as
+
+  company..employee.(@id &lt; 100)
+
+in this case, the @id is a `js2-xml-ref' that is part of an infix '&lt;'
+expression whose parent is a `js2-xml-dot-query-node'.&quot;
+  namespace
+  at-pos
+  colon-pos)
+
+(defsubst js2-xml-ref-node-attr-access-p (node)
+  &quot;Return non-nil if this expression began with an @-token.&quot;
+  (and (numberp (js2-xml-ref-node-at-pos node))
+       (plusp (js2-xml-ref-node-at-pos node))))
+
+(defstruct (js2-xml-prop-ref-node
+            (:include js2-xml-ref-node)
+            (:constructor nil)
+            (:constructor make-js2-xml-prop-ref-node (&amp;key (type js2-REF_NAME)
+                                                           (pos js2-token-beg)
+                                                           len
+                                                           propname
+                                                           namespace
+                                                           at-pos
+                                                           colon-pos)))
+  &quot;AST node for an E4X XML [expr] property-ref expression.
+The JavaScript syntax is an optional @, an optional ns::, and a name.
+
+  [ '@' ] [ name '::' ] name
+
+Examples include name, ns::name, ns::*, *::name, *::*, @attr, @ns::attr,
+@ns::*, @*::attr, @*::*, and @*.
+
+The node starts at the @ token, if present.  Otherwise it starts at the
+namespace name.  The node bounds extend through the closing right-bracket,
+or if it is missing due to a syntax error, through the end of the index
+expression.&quot;
+  propname)
+
+(put 'cl-struct-js2-xml-prop-ref-node 'js2-visitor 'js2-visit-xml-prop-ref-node)
+(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)))
+
+(defun js2-print-xml-prop-ref-node (n i)
+  (insert (js2-make-pad i))
+  (if (js2-xml-ref-node-attr-access-p n)
+      (insert &quot;@&quot;))
+  (when (js2-xml-prop-ref-node-namespace n)
+    (js2-print-ast (js2-xml-prop-ref-node-namespace n) 0)
+    (insert &quot;::&quot;))
+  (if (js2-xml-prop-ref-node-propname n)
+      (js2-print-ast (js2-xml-prop-ref-node-propname n) 0)))
+
+(defstruct (js2-xml-elem-ref-node
+            (:include js2-xml-ref-node)
+            (:constructor nil)
+            (:constructor make-js2-xml-elem-ref-node (&amp;key (type js2-REF_MEMBER)
+                                                           (pos js2-token-beg)
+                                                           len
+                                                           expr
+                                                           lb
+                                                           rb
+                                                           namespace
+                                                           at-pos
+                                                           colon-pos)))
+  &quot;AST node for an E4X XML [expr] member-ref expression.
+Syntax:
+
+ [ '@' ] [ name '::' ] '[' expr ']'
+
+Examples include ns::[expr], @ns::[expr], @[expr], *::[expr] and @*::[expr].
+
+Note that the form [expr] (i.e. no namespace or attribute-qualifier)
+is not a legal E4X XML element-ref expression, since it's already used
+for standard JavaScript element-get array indexing.  Hence, a
+`js2-xml-elem-ref-node' always has either the attribute-qualifier, a
+non-nil namespace node, or both.
+
+The node starts at the @ token, if present.  Otherwise it starts
+at the namespace name.  The node bounds extend through the closing
+right-bracket, or if it is missing due to a syntax error, through the
+end of the index expression.&quot;
+  expr  ; the bracketed index expression
+  lb
+  rb)
+
+(put 'cl-struct-js2-xml-elem-ref-node 'js2-visitor 'js2-visit-xml-elem-ref-node)
+(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)))
+
+(defun js2-print-xml-elem-ref-node (n i)
+  (insert (js2-make-pad i))
+  (if (js2-xml-ref-node-attr-access-p n)
+      (insert &quot;@&quot;))
+  (when (js2-xml-elem-ref-node-namespace n)
+    (js2-print-ast (js2-xml-elem-ref-node-namespace n) 0)
+    (insert &quot;::&quot;))
+  (insert &quot;[&quot;)
+  (if (js2-xml-elem-ref-node-expr n)
+      (js2-print-ast (js2-xml-elem-ref-node-expr n) 0))
+  (insert &quot;]&quot;))
+
+;;; Placeholder nodes for when we try parsing the XML literals structurally.
+
+(defstruct (js2-xml-start-tag-node
+            (:include js2-xml-node)
+            (:constructor nil)
+            (:constructor make-js2-xml-start-tag-node (&amp;key (type js2-XML)
+                                                            (pos js2-ts-cursor)
+                                                            len
+                                                            name
+                                                            attrs
+                                                            kids
+                                                            empty-p)))
+  &quot;AST node for an XML start-tag.  Not currently used.
+The `kids' field is a lisp list of child content nodes.&quot;
+  name      ; a `js2-xml-name-node'
+  attrs     ; a lisp list of `js2-xml-attr-node'
+  empty-p)  ; t if this is an empty element such as &lt;foo bar=&quot;baz&quot;/&gt;
+
+(put 'cl-struct-js2-xml-start-tag-node 'js2-visitor 'js2-visit-xml-start-tag)
+(put 'cl-struct-js2-xml-start-tag-node 'js2-printer 'js2-print-xml-start-tag)
+
+(defun js2-visit-xml-start-tag (n v)
+  (js2-visit-ast (js2-xml-start-tag-node-name n) v)
+  (dolist (attr (js2-xml-start-tag-node-attrs n))
+    (js2-visit-ast attr v))
+  (js2-visit-block n v))
+
+(defun js2-print-xml-start-tag (n i)
+  (insert (js2-make-pad i) &quot;&lt;&quot;)
+  (js2-print-ast (js2-xml-start-tag-node-name n) 0)
+  (when (js2-xml-start-tag-node-attrs n)
+    (insert &quot; &quot;)
+    (js2-print-list (js2-xml-start-tag-node-attrs n) &quot; &quot;))
+  (insert &quot;&gt;&quot;))
+
+;; I -think- I'm going to make the parent node the corresponding start-tag,
+;; and add the end-tag to the kids list of the parent as well.
+(defstruct (js2-xml-end-tag-node
+            (:include js2-xml-node)
+            (:constructor nil)
+            (:constructor make-js2-xml-end-tag-node (&amp;key (type js2-XML)
+                                                          (pos js2-ts-cursor)
+                                                          len
+                                                          name)))
+  &quot;AST node for an XML end-tag.  Not currently used.&quot;
+  name)  ; a `js2-xml-name-node'
+
+(put 'cl-struct-js2-xml-end-tag-node 'js2-visitor 'js2-visit-xml-end-tag)
+(put 'cl-struct-js2-xml-end-tag-node 'js2-printer 'js2-print-xml-end-tag)
+
+(defun js2-visit-xml-end-tag (n v)
+  (js2-visit-ast (js2-xml-end-tag-node-name n) v))
+
+(defun js2-print-xml-end-tag (n i)
+  (insert (js2-make-pad i))
+  (insert &quot;&lt;/&quot;)
+  (js2-print-ast (js2-xml-end-tag-node-name n) 0)
+  (insert &quot;&gt;&quot;))
+
+(defstruct (js2-xml-name-node
+            (:include js2-xml-node)
+            (:constructor nil)
+            (:constructor make-js2-xml-name-node (&amp;key (type js2-XML)
+                                                       (pos js2-ts-cursor)
+                                                       len
+                                                       namespace
+                                                       kids)))
+  &quot;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.
+The kids field is a list of `js2-name-node' and `js2-xml-js-expr-node'.
+For a simple name, the kids list has exactly one node, a `js2-name-node'.&quot;
+  namespace)  ; a `js2-string-node'
+
+(put 'cl-struct-js2-xml-name-node 'js2-visitor 'js2-visit-xml-name-node)
+(put 'cl-struct-js2-xml-name-node 'js2-printer 'js2-print-xml-name-node)
+
+(defun js2-visit-xml-name-node (n v)
+  (js2-visit-ast (js2-xml-name-node-namespace n) v))
+
+(defun js2-print-xml-name-node (n i)
+  (insert (js2-make-pad i))
+  (when (js2-xml-name-node-namespace n)
+    (js2-print-ast (js2-xml-name-node-namespace n) 0)
+    (insert &quot;::&quot;))
+  (dolist (kid (js2-xml-name-node-kids n))
+    (js2-print-ast kid 0)))
+
+(defstruct (js2-xml-pi-node
+            (:include js2-xml-node)
+            (:constructor nil)
+            (:constructor make-js2-xml-pi-node (&amp;key (type js2-XML)
+                                                     (pos js2-ts-cursor)
+                                                     len
+                                                     name
+                                                     attrs)))
+  &quot;AST node for an E4X XML processing instruction.  Not currently used.&quot;
+  name   ; a `js2-xml-name-node'
+  attrs) ; a list of `js2-xml-attr-node'
+
+(put 'cl-struct-js2-xml-pi-node 'js2-visitor 'js2-visit-xml-pi-node)
+(put 'cl-struct-js2-xml-pi-node 'js2-printer 'js2-print-xml-pi-node)
+
+(defun js2-visit-xml-pi-node (n v)
+  (js2-visit-ast (js2-xml-pi-node-name n) v)
+  (dolist (attr (js2-xml-pi-node-attrs n))
+    (js2-visit-ast attr v)))
+
+(defun js2-print-xml-pi-node (n i)
+  (insert (js2-make-pad i) &quot;&lt;?&quot;)
+  (js2-print-ast (js2-xml-pi-node-name n))
+  (when (js2-xml-pi-node-attrs n)
+    (insert &quot; &quot;)
+    (js2-print-list (js2-xml-pi-node-attrs n)))
+  (insert &quot;?&gt;&quot;))
+
+(defstruct (js2-xml-cdata-node
+            (:include js2-xml-node)
+            (:constructor nil)
+            (:constructor make-js2-xml-cdata-node (&amp;key (type js2-XML)
+                                                        (pos js2-ts-cursor)
+                                                        len
+                                                        content)))
+  &quot;AST node for a CDATA escape section.  Not currently used.&quot;
+  content)  ; a `js2-string-node' with node-property 'quote-type 'cdata
+
+(put 'cl-struct-js2-xml-cdata-node 'js2-visitor 'js2-visit-xml-cdata-node)
+(put 'cl-struct-js2-xml-cdata-node 'js2-printer 'js2-print-xml-cdata-node)
+
+(defun js2-visit-xml-cdata-node (n v)
+  (js2-visit-ast (js2-xml-cdata-node-content n) v))
+
+(defun js2-print-xml-cdata-node (n i)
+  (insert (js2-make-pad i))
+  (js2-print-ast (js2-xml-cdata-node-content n)))
+
+(defstruct (js2-xml-attr-node
+            (:include js2-xml-node)
+            (:constructor nil)
+            (:constructor make-js2-attr-node (&amp;key (type js2-XML)
+                                                   (pos js2-ts-cursor)
+                                                   len
+                                                   name
+                                                   value
+                                                   eq-pos
+                                                   quote-type)))
+  &quot;AST node representing a foo='bar' XML attribute value.  Not yet used.&quot;
+  name   ; a `js2-xml-name-node'
+  value  ; a `js2-xml-name-node'
+  eq-pos ; buffer position of &quot;=&quot; sign
+  quote-type) ; 'single or 'double
+
+(put 'cl-struct-js2-xml-attr-node 'js2-visitor 'js2-visit-xml-attr-node)
+(put 'cl-struct-js2-xml-attr-node 'js2-printer 'js2-print-xml-attr-node)
+
+(defun js2-visit-xml-attr-node (n v)
+  (js2-visit-ast (js2-xml-attr-node-name n) v)
+  (js2-visit-ast (js2-xml-attr-node-value n) v))
+
+(defun js2-print-xml-attr-node (n i)
+  (let ((quote (if (eq (js2-xml-attr-node-quote-type n) 'single)
+                   &quot;'&quot;
+                 &quot;\&quot;&quot;)))
+    (insert (js2-make-pad i))
+    (js2-print-ast (js2-xml-attr-node-name n) 0)
+    (insert &quot;=&quot; quote)
+    (js2-print-ast (js2-xml-attr-node-value n) 0)
+    (insert quote)))
+
+(defstruct (js2-xml-text-node
+            (:include js2-xml-node)
+            (:constructor nil)
+            (:constructor make-js2-text-node (&amp;key (type js2-XML)
+                                                   (pos js2-ts-cursor)
+                                                   len
+                                                   content)))
+  &quot;AST node for an E4X XML text node.  Not currently used.&quot;
+  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)
+
+(defun js2-visit-xml-text-node (n v)
+  (js2-visit-ast (js2-xml-text-node-content n) v))
+
+(defun js2-print-xml-text-node (n i)
+  (insert (js2-make-pad i))
+  (dolist (kid (js2-xml-text-node-content n))
+    (js2-print-ast kid)))
+
+(defstruct (js2-xml-comment-node
+            (:include js2-xml-node)
+            (:constructor nil)
+            (:constructor make-js2-xml-comment-node (&amp;key (type js2-XML)
+                                                          (pos js2-ts-cursor)
+                                                          len)))
+  &quot;AST node for E4X XML comment.  Not currently used.&quot;)
+
+(put 'cl-struct-js2-xml-comment-node 'js2-visitor 'js2-visit-none)
+(put 'cl-struct-js2-xml-comment-node 'js2-printer 'js2-print-xml-comment)
+
+(defun js2-print-xml-comment (n i)
+  (insert (js2-make-pad i)
+          (js2-node-string n)))
+
+;;; Node utilities
+
+(defsubst js2-node-line (n)
+  &quot;Fetch the source line number at the start of node N.
+This is O(n) in the length of the source buffer; use prudently.&quot;
+  (1+ (count-lines (point-min) (js2-node-abs-pos n))))
+
+(defsubst js2-block-node-kid (n i)
+  &quot;Return child I of node N, or nil if there aren't that many.&quot;
+  (nth i (js2-block-node-kids n)))
+
+(defsubst js2-block-node-first (n)
+  &quot;Return first child of block node N, or nil if there is none.&quot;
+  (first (js2-block-node-kids n)))
+
+(defun js2-node-root (n)
+  &quot;Return the root of the AST containing N.
+If N has no parent pointer, returns N.&quot;
+  (let ((parent (js2-node-parent n)))
+    (if parent
+        (js2-node-root parent)
+      n)))
+
+(defun js2-node-position-in-parent (node &amp;optional parent)
+  &quot;Return the position of NODE in parent's block-kids list.
+PARENT can be supplied if known.  Positioned returned is zero-indexed.
+Returns 0 if NODE is not a child of a block statement, or if NODE
+is not a statement node.&quot;
+  (let ((p (or parent (js2-node-parent node)))
+        (i 0))
+    (if (not (js2-block-node-p p))
+        i
+      (or (js2-position node (js2-block-node-kids p))
+          0))))
+
+(defsubst js2-node-short-name (n)
+  &quot;Return the short name of node N as a string, e.g. `js2-if-node'.&quot;
+  (substring (symbol-name (aref n 0))
+             (length &quot;cl-struct-&quot;)))
+
+(defsubst js2-node-child-list (node)
+  &quot;Return the child list for NODE, a lisp list of nodes.
+Works for block nodes, array nodes, obj literals, funarg lists,
+var decls and try nodes (for catch clauses).  Note that you should call
+`js2-block-node-kids' on the function body for the body statements.
+Returns nil for zero-length child lists or unsupported nodes.&quot;
+  (cond
+   ((js2-function-node-p node)
+    (js2-function-node-params node))
+   ((js2-block-node-p node)
+    (js2-block-node-kids node))
+   ((js2-try-node-p node)
+    (js2-try-node-catch-clauses node))
+   ((js2-array-node-p node)
+    (js2-array-node-elems node))
+   ((js2-object-node-p node)
+    (js2-object-node-elems node))
+   ((js2-call-node-p node)
+    (js2-call-node-args node))
+   ((js2-new-node-p node)
+    (js2-new-node-args node))
+   ((js2-var-decl-node-p node)
+    (js2-var-decl-node-kids node))
+   (t
+    nil)))
+
+(defsubst js2-node-set-child-list (node kids)
+  &quot;Set the child list for NODE to KIDS.&quot;
+   (cond
+    ((js2-function-node-p node)
+     (setf (js2-function-node-params node) kids))
+    ((js2-block-node-p node)
+     (setf (js2-block-node-kids node) kids))
+    ((js2-try-node-p node)
+     (setf (js2-try-node-catch-clauses node) kids))
+    ((js2-array-node-p node)
+     (setf (js2-array-node-elems node) kids))
+    ((js2-object-node-p node)
+     (setf (js2-object-node-elems node) kids))
+    ((js2-call-node-p node)
+     (setf (js2-call-node-args node) kids))
+    ((js2-new-node-p node)
+     (setf (js2-new-node-args node) kids))
+    ((js2-var-decl-node-p node)
+     (setf (js2-var-decl-node-kids node) kids))
+    (t
+     (error &quot;Unsupported node type: %s&quot; (js2-node-short-name node))))
+   kids)
+
+;; All because Common Lisp doesn't support multiple inheritance for defstructs.
+(defconst js2-paren-expr-nodes
+  '(cl-struct-js2-array-comp-loop-node
+    cl-struct-js2-array-comp-node
+    cl-struct-js2-call-node
+    cl-struct-js2-catch-node
+    cl-struct-js2-do-node
+    cl-struct-js2-elem-get-node
+    cl-struct-js2-for-in-node
+    cl-struct-js2-for-node
+    cl-struct-js2-function-node
+    cl-struct-js2-if-node
+    cl-struct-js2-let-node
+    cl-struct-js2-new-node
+    cl-struct-js2-paren-node
+    cl-struct-js2-switch-node
+    cl-struct-js2-while-node
+    cl-struct-js2-with-node
+    cl-struct-js2-xml-dot-query-node)
+  &quot;Node types that can have a parenthesized child expression.
+In particular, nodes that respond to `js2-node-lp' and `js2-node-rp'.&quot;)
+
+(defsubst js2-paren-expr-node-p (node)
+  &quot;Return t for nodes that typically have a parenthesized child expression.
+Useful for computing the indentation anchors for arg-lists and conditions.
+Note that it may return a false positive, for instance when NODE is
+a `js2-new-node' and there are no arguments or parentheses.&quot;
+  (memq (aref node 0) js2-paren-expr-nodes))
+
+;; Fake polymorphism... yech.
+(defsubst js2-node-lp (node)
+  &quot;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.&quot;
+  (cond
+   ((js2-elem-get-node-p node)
+    (js2-elem-get-node-lb node))
+   ((js2-loop-node-p node)
+    (js2-loop-node-lp node))
+   ((js2-function-node-p node)
+    (js2-function-node-lp node))
+   ((js2-if-node-p node)
+    (js2-if-node-lp node))
+   ((js2-new-node-p node)
+    (js2-new-node-lp node))
+   ((js2-call-node-p node)
+    (js2-call-node-lp node))
+   ((js2-paren-node-p node)
+    (js2-node-pos node))
+   ((js2-switch-node-p node)
+    (js2-switch-node-lp node))
+   ((js2-catch-node-p node)
+    (js2-catch-node-lp node))
+   ((js2-let-node-p node)
+    (js2-let-node-lp node))
+   ((js2-array-comp-node-p node)
+    (js2-array-comp-node-lp node))
+   ((js2-with-node-p node)
+    (js2-with-node-lp node))
+   ((js2-xml-dot-query-node-p node)
+    (1+ (js2-infix-node-op-pos node)))
+   (t
+    (error &quot;Unsupported node type: %s&quot; (js2-node-short-name node)))))
+
+;; Fake polymorphism... blech.
+(defsubst js2-node-rp (node)
+  &quot;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.&quot;
+  (cond
+   ((js2-elem-get-node-p node)
+    (js2-elem-get-node-lb node))
+   ((js2-loop-node-p node)
+    (js2-loop-node-rp node))
+   ((js2-function-node-p node)
+    (js2-function-node-rp node))
+   ((js2-if-node-p node)
+    (js2-if-node-rp node))
+   ((js2-new-node-p node)
+    (js2-new-node-rp node))
+   ((js2-call-node-p node)
+    (js2-call-node-rp node))
+   ((js2-paren-node-p node)
+    (+ (js2-node-pos node) (js2-node-len node)))
+   ((js2-switch-node-p node)
+    (js2-switch-node-rp node))
+   ((js2-catch-node-p node)
+    (js2-catch-node-rp node))
+   ((js2-let-node-p node)
+    (js2-let-node-rp node))
+   ((js2-array-comp-node-p node)
+    (js2-array-comp-node-rp node))
+   ((js2-with-node-p node)
+    (js2-with-node-rp node))
+   ((js2-xml-dot-query-node-p node)
+    (1+ (js2-xml-dot-query-node-rp node)))
+   (t
+    (error &quot;Unsupported node type: %s&quot; (js2-node-short-name node)))))
+
+(defsubst js2-node-first-child (node)
+  &quot;Returns the first element of `js2-node-child-list' for NODE.&quot;
+  (car (js2-node-child-list node)))
+
+(defsubst js2-node-last-child (node)
+  &quot;Returns the last element of `js2-node-last-child' for NODE.&quot;
+  (car (last (js2-node-child-list node))))
+
+(defun js2-node-prev-sibling (node)
+  &quot;Return the previous statement in parent.
+Works for parents supported by `js2-node-child-list'.
+Returns nil if NODE is not in the parent, or PARENT is
+not a supported node, or if NODE is the first child.&quot;
+  (let* ((p (js2-node-parent node))
+         (kids (js2-node-child-list p))
+         (sib (car kids)))
+    (while (and kids
+                (not (eq node (cadr kids))))
+      (setq kids (cdr kids)
+            sib (car kids)))
+    sib))
+
+(defun js2-node-next-sibling (node)
+  &quot;Return the next statement in parent block.
+Returns nil if NODE is not in the block, or PARENT is not
+a block node, or if NODE is the last statement.&quot;
+  (let* ((p (js2-node-parent node))
+         (kids (js2-node-child-list p)))
+    (while (and kids
+                (not (eq node (car kids))))
+      (setq kids (cdr kids)))
+    (cadr kids)))
+
+(defun js2-node-find-child-before (pos parent &amp;optional after)
+  &quot;Find the last child that starts before POS in parent.
+If AFTER is non-nil, returns first child starting after POS.
+POS is an absolute buffer position.  PARENT is any node
+supported by `js2-node-child-list'.
+Returns nil if no applicable child is found.&quot;
+  (let ((kids (if (js2-function-node-p parent)
+                  (js2-block-node-kids (js2-function-node-body parent))
+                (js2-node-child-list parent)))
+        (beg (if (js2-function-node-p parent)
+                 (js2-node-abs-pos (js2-function-node-body parent))
+               (js2-node-abs-pos parent)))
+        kid
+        result
+        fn
+        (continue t))
+    (setq fn (if after '&gt; '&lt;))
+    (while (and kids continue)
+      (setq kid (car kids))
+      (if (funcall fn (+ beg (js2-node-pos kid)) pos)
+          (setq result kid
+                continue (if after nil t))
+        (setq continue (if after t nil)))
+      (setq kids (cdr kids)))
+    result))
+
+(defun js2-node-find-child-after (pos parent)
+  &quot;Find first child that starts after POS in parent.
+POS is an absolute buffer position.  PARENT is any node
+supported by `js2-node-child-list'.
+Returns nil if no applicable child is found.&quot;
+  (js2-node-find-child-before pos parent 'after))
+
+(defun js2-node-replace-child (pos parent new-node)
+  &quot;Replace node at index POS in PARENT with NEW-NODE.
+Only works for parents supported by `js2-node-child-list'.&quot;
+  (let ((kids (js2-node-child-list parent))
+        (i 0))
+    (while (&lt; i pos)
+      (setq kids (cdr kids)
+            i (1+ i)))
+    (setcar kids new-node)
+    (js2-node-add-children parent new-node)))
+
+(defun js2-node-buffer (n)
+  &quot;Return the buffer associated with AST N.
+Returns nil if the buffer is not set as a property on the root
+node, or if parent links were not recorded during parsing.&quot;
+  (let ((root (js2-node-root n)))
+    (and root
+         (js2-ast-root-p root)
+         (js2-ast-root-buffer root))))
+
+(defsubst js2-block-node-push (n kid)
+  &quot;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.&quot;
+  (let ((kids (js2-node-child-list n)))
+    (if kids
+        (setcdr kids (nconc (cdr kids) (list kid)))
+      (js2-node-set-child-list n (list kid)))
+    (js2-node-add-children n kid)))
+
+(defun js2-node-string (node)
+  (let ((buf (js2-node-buffer node))
+        pos)
+    (unless buf
+      (error &quot;No buffer available for node %s&quot; node))
+    (save-excursion
+      (set-buffer buf)
+      (buffer-substring-no-properties (setq pos (js2-node-abs-pos node))
+                                      (+ pos (js2-node-len node))))))
+
+;; Container for storing the node we're looking for in a traversal.
+(js2-deflocal js2-discovered-node nil)
+
+;; Keep track of absolute node position during traversals.
+(js2-deflocal js2-visitor-offset nil)
+
+(js2-deflocal js2-node-search-point nil)
+
+(when js2-mode-dev-mode-p
+  (defun js2-find-node-at-point ()
+    (interactive)
+    (let ((node (js2-node-at-point)))
+      (message &quot;%s&quot; (or node &quot;No node found at point&quot;))))
+  (defun js2-node-name-at-point ()
+    (interactive)
+    (let ((node (js2-node-at-point)))
+      (message &quot;%s&quot; (if node
+                        (js2-node-short-name node)
+                      &quot;No node found at point.&quot;)))))
+
+(defun js2-node-at-point (&amp;optional pos skip-comments)
+  &quot;Return AST node at POS, a buffer position, defaulting to current point.
+The `js2-mode-ast' variable must be set to the current parse tree.
+Signals an error if the AST (`js2-mode-ast') is nil.
+Always returns a node - if it can't find one, it returns the root.
+If SKIP-COMMENTS is non-nil, comment nodes are ignored.&quot;
+  (let ((ast js2-mode-ast)
+        result)
+    (unless ast
+      (error &quot;No JavaScript AST available&quot;))
+    ;; Look through comments first, since they may be inside nodes that
+    ;; would otherwise report a match.
+    (setq pos (or pos (point))
+          result (if (&gt; pos (js2-node-abs-end ast))
+                     ast
+                   (if (not skip-comments)
+                       (js2-comment-at-point pos))))
+    (unless result
+      (setq js2-discovered-node nil
+            js2-visitor-offset 0
+            js2-node-search-point pos)
+      (unwind-protect
+          (catch 'js2-visit-done
+            (js2-visit-ast ast #'js2-node-at-point-visitor))
+        (setq js2-visitor-offset nil
+              js2-node-search-point nil))
+      (setq result js2-discovered-node))
+    ;; may have found a comment beyond end of last child node,
+    ;; since visiting the ast-root looks at the comment-list last.
+    (if (and skip-comments
+             (js2-comment-node-p result))
+        (setq result nil))
+    (or result js2-mode-ast)))
+
+(defun js2-node-at-point-visitor (node end-p)
+  (let ((rel-pos (js2-node-pos node))
+        abs-pos
+        abs-end
+        (point js2-node-search-point))
+    (cond
+     (end-p
+      ;; this evaluates to a non-nil return value, even if it's zero
+      (decf js2-visitor-offset rel-pos))
+     ;; we already looked for comments before visiting, and don't want them now
+     ((js2-comment-node-p node)
+      nil)
+     (t
+      (setq abs-pos (incf js2-visitor-offset rel-pos)
+            ;; we only want to use the node if the point is before
+            ;; the last character position in the node, so we decrement
+            ;; the absolute end by 1.
+            abs-end (+ abs-pos (js2-node-len node) -1))
+      (cond
+       ;; If this node starts after search-point, stop the search.
+       ((&gt; abs-pos point)
+        (throw 'js2-visit-done nil))
+       ;; If this node ends before the search-point, don't check kids.
+       ((&gt; point abs-end)
+        nil)
+       (t
+        ;; Otherwise point is within this node, possibly in a child.
+        (setq js2-discovered-node node)
+        t))))))  ; keep processing kids to look for more specific match
+
+(defsubst js2-block-comment-p (node)
+  &quot;Return non-nil if NODE is a comment node of format `jsdoc' or `block'.&quot;
+  (and (js2-comment-node-p node)
+       (memq (js2-comment-node-format node) '(jsdoc block))))
+
+;; TODO:  put the comments in a vector and binary-search them instead
+(defun js2-comment-at-point (&amp;optional pos)
+  &quot;Look through scanned comment nodes for one containing POS.
+POS is a buffer position that defaults to current point.
+Function returns nil if POS was not in any comment node.&quot;
+  (let ((ast js2-mode-ast)
+        (x (or pos (point)))
+        beg
+        end)
+    (unless ast
+      (error &quot;No JavaScript AST available&quot;))
+    (catch 'done
+      ;; Comments are stored in lexical order.
+      (dolist (comment (js2-ast-root-comments ast) nil)
+        (setq beg (js2-node-abs-pos comment)
+              end (+ beg (js2-node-len comment)))
+        (if (and (&gt;= x beg)
+                 (&lt;= x end))
+            (throw 'done comment))))))
+
+(defun js2-mode-find-parent-fn (node)
+  &quot;Find function enclosing NODE.
+Returns nil if NODE is not inside a function.&quot;
+  (setq node (js2-node-parent node))
+  (while (and node (not (js2-function-node-p node)))
+    (setq node (js2-node-parent node)))
+  (and (js2-function-node-p node) node))
+
+(defun js2-mode-find-enclosing-fn (node)
+  &quot;Find function or root enclosing NODE.&quot;
+  (if (js2-ast-root-p node)
+      node
+    (setq node (js2-node-parent node))
+    (while (not (or (js2-ast-root-p node)
+                    (js2-function-node-p node)))
+      (setq node (js2-node-parent node)))
+    node))
+
+(defun js2-mode-find-enclosing-node (beg end)
+  &quot;Find script or function fully enclosing BEG and END.&quot;
+  (let ((node (js2-node-at-point beg))
+        pos
+        (continue t))
+    (while continue
+      (if (or (js2-ast-root-p node)
+              (and (js2-function-node-p node)
+                   (&lt;= (setq pos (js2-node-abs-pos node)) beg)
+                   (&gt;= (+ pos (js2-node-len node)) end)))
+          (setq continue nil)
+        (setq node (js2-node-parent node))))
+    node))
+
+(defun js2-node-parent-script-or-fn (node)
+  &quot;Find script or function immediately enclosing NODE.
+If NODE is the ast-root, returns nil.&quot;
+  (if (js2-ast-root-p node)
+      nil
+    (setq node (js2-node-parent node))
+    (while (and node (not (or (js2-function-node-p node)
+                              (js2-script-node-p node))))
+      (setq node (js2-node-parent node)))
+    node))
+
+(defsubst js2-nested-function-p (node)
+  &quot;Return t if NODE is a nested function, or is inside a nested function.&quot;
+  (js2-function-node-p (if (js2-function-node-p node)
+                           (js2-node-parent-script-or-fn node)
+                         (js2-node-parent-script-or-fn
+                          (js2-node-parent-script-or-fn node)))))
+
+(defsubst js2-function-param-node-p (node)
+  &quot;Return non-nil if NODE is a param node of a `js2-function-node'.&quot;
+  (let ((parent (js2-node-parent node)))
+    (and parent
+         (js2-function-node-p parent)
+         (memq node (js2-function-node-params parent)))))
+
+(defsubst js2-mode-shift-kids (kids start offset)
+  (dolist (kid kids)
+    (if (&gt; (js2-node-pos kid) start)
+        (incf (js2-node-pos kid) offset))))
+
+(defsubst js2-mode-shift-children (parent start offset)
+  &quot;Update start-positions of all children of PARENT beyond START.&quot;
+  (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)
+  &quot;Return t if NODE is a descendant of ANCESTOR.&quot;
+  (while (and node
+              (not (eq node ancestor)))
+    (setq node (js2-node-parent node)))
+  node)
+
+;;; visitor infrastructure
+
+(defun js2-visit-none (node callback)
+  &quot;Visitor for AST node that have no node children.&quot;
+  nil)
+
+(defun js2-print-none (node indent)
+  &quot;Visitor for AST node with no printed representation.&quot;)
+
+(defun js2-print-body (node indent)
+  &quot;Print a statement, or a block without braces.&quot;
+  (if (js2-block-node-p node)
+      (dolist (kid (js2-block-node-kids node))
+        (js2-print-ast kid indent))
+    (js2-print-ast node indent)))
+
+(defun js2-print-list (args &amp;optional delimiter)
+  (loop with len = (length args)
+        for arg in args
+        for count from 1
+        do
+        (js2-print-ast arg 0)
+        (if (&lt; count len)
+            (insert (or delimiter &quot;, &quot;)))))
+
+(defun js2-print-tree (ast)
+  &quot;Prints an AST to the current buffer.
+Makes `js2-ast-parent-nodes' available to the printer functions.&quot;
+  (let ((max-lisp-eval-depth (max max-lisp-eval-depth 1500)))
+    (js2-print-ast ast)))
+
+(defun js2-print-ast (node &amp;optional indent)
+  &quot;Helper function for printing AST nodes.
+Requires `js2-ast-parent-nodes' to be non-nil.
+You should use `js2-print-tree' instead of this function.&quot;
+  (let ((printer (get (aref node 0) 'js2-printer))
+        (i (or indent 0))
+        (pos (js2-node-abs-pos node)))
+    ;; TODO:  wedge comments in here somewhere
+    (if printer
+        (funcall printer node i))))
+
+(defconst js2-side-effecting-tokens
+  (let ((tokens (make-bool-vector js2-num-tokens nil)))
+    (dolist (tt (list js2-ASSIGN
+                      js2-ASSIGN_ADD
+                      js2-ASSIGN_BITAND
+                      js2-ASSIGN_BITOR
+                      js2-ASSIGN_BITXOR
+                      js2-ASSIGN_DIV
+                      js2-ASSIGN_LSH
+                      js2-ASSIGN_MOD
+                      js2-ASSIGN_MUL
+                      js2-ASSIGN_RSH
+                      js2-ASSIGN_SUB
+                      js2-ASSIGN_URSH
+                      js2-BLOCK
+                      js2-BREAK
+                      js2-CALL
+                      js2-CATCH
+                      js2-CATCH_SCOPE
+                      js2-CONST
+                      js2-CONTINUE
+                      js2-DEBUGGER
+                      js2-DEC
+                      js2-DELPROP
+                      js2-DEL_REF
+                      js2-DO
+                      js2-ELSE
+                      js2-EMPTY
+                      js2-ENTERWITH
+                      js2-EXPORT
+                      js2-EXPR_RESULT
+                      js2-FINALLY
+                      js2-FOR
+                      js2-FUNCTION
+                      js2-GOTO
+                      js2-IF
+                      js2-IFEQ
+                      js2-IFNE
+                      js2-IMPORT
+                      js2-INC
+                      js2-JSR
+                      js2-LABEL
+                      js2-LEAVEWITH
+                      js2-LET
+                      js2-LETEXPR
+                      js2-LOCAL_BLOCK
+                      js2-LOOP
+                      js2-NEW
+                      js2-REF_CALL
+                      js2-RETHROW
+                      js2-RETURN
+                      js2-RETURN_RESULT
+                      js2-SEMI
+                      js2-SETELEM
+                      js2-SETELEM_OP
+                      js2-SETNAME
+                      js2-SETPROP
+                      js2-SETPROP_OP
+                      js2-SETVAR
+                      js2-SET_REF
+                      js2-SET_REF_OP
+                      js2-SWITCH
+                      js2-TARGET
+                      js2-THROW
+                      js2-TRY
+                      js2-VAR
+                      js2-WHILE
+                      js2-WITH
+                      js2-WITHEXPR
+                      js2-YIELD))
+      (aset tokens tt t))
+    (if js2-instanceof-has-side-effects
+        (aset tokens js2-INSTANCEOF t))
+    tokens))
+
+(defun js2-node-has-side-effects (node)
+  &quot;Return t if NODE has side effects.&quot;
+  (when node  ; makes it easier to handle malformed expressions
+    (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.
+       ;; 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)))
+       ((= tt js2-COMMA)
+        (js2-node-has-side-effects (js2-infix-node-right node)))
+       ((or (= tt js2-AND)
+            (= tt js2-OR))
+        (or (js2-node-has-side-effects (js2-infix-node-right node))
+            (js2-node-has-side-effects (js2-infix-node-left node))))
+       ((= tt js2-HOOK)
+        (and (js2-node-has-side-effects (js2-cond-node-true-expr node))
+             (js2-node-has-side-effects (js2-cond-node-false-expr node))))
+       ((js2-paren-node-p node)
+        (js2-node-has-side-effects (js2-paren-node-expr node)))
+       ((= tt js2-ERROR) ; avoid cascaded error messages
+        nil)
+       (t
+        (aref js2-side-effecting-tokens tt))))))
+
+(defun js2-member-expr-leftmost-name (node)
+  &quot;For an expr such as foo.bar.baz, return leftmost node foo.
+NODE is any `js2-node' object.  If it represents a member expression,
+which is any sequence of property gets, element-gets, function calls,
+or xml descendants/filter operators, then we look at the lexically
+leftmost (first) node in the chain.  If it is a name-node we return it.
+Note that NODE can be a raw name-node and it will be returned as well.
+If NODE is not a name-node or member expression, or if it is a member
+expression whose leftmost target is not a name node, returns nil.&quot;
+  (let ((continue t)
+        result)
+    (while (and continue (not result))
+      (cond
+       ((js2-name-node-p node)
+        (setq result node))
+       ((js2-prop-get-node-p node)
+        (setq node (js2-prop-get-node-left node)))
+       ;; TODO:  handle call-nodes, xml-nodes, others?
+       (t
+        (setq continue nil))))
+    result))
+
+(defconst js2-stmt-node-types
+  (list js2-BLOCK
+        js2-BREAK
+        js2-CONTINUE
+        js2-DEFAULT  ; e4x &quot;default xml namespace&quot; statement
+        js2-DO
+        js2-EXPR_RESULT
+        js2-EXPR_VOID
+        js2-FOR
+        js2-IF
+        js2-RETURN
+        js2-SWITCH
+        js2-THROW
+        js2-TRY
+        js2-WHILE
+        js2-WITH)
+  &quot;Node types that only appear in statement contexts.
+The list does not include nodes that always appear as the child
+of another specific statement type, such as switch-cases,
+catch and finally blocks, and else-clauses.  The list also excludes
+nodes like yield, let and var, which may appear in either expression
+or statement context, and in the latter context always have a
+`js2-expr-stmt-node' parent.  Finally, the list does not include
+functions or scripts, which are treated separately from statements
+by the JavaScript parser and runtime.&quot;)
+
+(defun js2-stmt-node-p (node)
+  &quot;Heuristic for figuring out if NODE is a statement.
+Some node types can appear in either an expression context or a
+statement context, e.g. let-nodes, yield-nodes, and var-decl nodes.
+For these node types in a statement context, the parent will be a
+`js2-expr-stmt-node'.
+Functions aren't included in the check.&quot;
+  (memq (js2-node-type node) js2-stmt-node-types))
+
+(defsubst js2-mode-find-first-stmt (node)
+  &quot;Search upward starting from NODE looking for a statement.
+For purposes of this function, a `js2-function-node' counts.&quot;
+  (while (not (or (js2-stmt-node-p node)
+                  (js2-function-node-p node)))
+    (setq node (js2-node-parent node)))
+  node)
+
+(defun js2-node-parent-stmt (node)
+  &quot;Return the node's first ancestor that is a statement.
+Returns nil if NODE is a `js2-ast-root'.  Note that any expression
+appearing in a statement context will have a parent that is a
+`js2-expr-stmt-node' that will be returned by this function.&quot;
+  (let ((parent (js2-node-parent node)))
+    (if (or (null parent)
+            (js2-stmt-node-p parent)
+            (and (js2-function-node-p parent)
+                 (not (eq (js2-function-node-form parent)
+                          'FUNCTION_EXPRESSION))))
+        parent
+      (js2-node-parent-stmt parent))))
+
+;; In the Mozilla Rhino sources, Roshan James writes:
+;;  Does consistent-return analysis on the function body when strict mode is
+;;  enabled.
+;;
+;;    function (x) { return (x+1) }
+;;
+;;  is ok, but
+;;
+;;    function (x) { if (x &lt; 0) return (x+1); }
+;;
+;;  is not because the function can potentially return a value when the
+;;  condition is satisfied and if not, the function does not explicitly
+;;  return a value.
+;;
+;;  This extends to checking mismatches such as &quot;return&quot; and &quot;return &lt;value&gt;&quot;
+;;  used in the same function. Warnings are not emitted if inconsistent
+;;  returns exist in code that can be statically shown to be unreachable.
+;;  Ex.
+;;    function (x) { while (true) { ... if (..) { return value } ... } }
+;;
+;;  emits no warning. However if the loop had a break statement, then a
+;;  warning would be emitted.
+;;
+;;  The consistency analysis looks at control structures such as loops, ifs,
+;;  switch, try-catch-finally blocks, examines the reachable code paths and
+;;  warns the user about an inconsistent set of termination possibilities.
+;;
+;;  These flags enumerate the possible ways a statement/function can
+;;  terminate. These flags are used by endCheck() and by the Parser to
+;;  detect inconsistent return usage.
+;;
+;;  END_UNREACHED is reserved for code paths that are assumed to always be
+;;  able to execute (example: throw, continue)
+;;
+;;  END_DROPS_OFF indicates if the statement can transfer control to the
+;;  next one. Statement such as return dont. A compound statement may have
+;;  some branch that drops off control to the next statement.
+;;
+;;  END_RETURNS indicates that the statement can return with no value.
+;;  END_RETURNS_VALUE indicates that the statement can return a value.
+;;
+;;  A compound statement such as
+;;  if (condition) {
+;;    return value;
+;;  }
+;;  Will be detected as (END_DROPS_OFF | END_RETURN_VALUE) by endCheck()
+
+(defconst js2-END_UNREACHED 0)
+(defconst js2-END_DROPS_OFF 1)
+(defconst js2-END_RETURNS 2)
+(defconst js2-END_RETURNS_VALUE 4)
+(defconst js2-END_YIELDS 8)
+
+(defun js2-has-consistent-return-usage (node)
+  &quot;Check that every return usage in a function body is consistent.
+Returns t if the function satisfies strict mode requirement.&quot;
+  (let ((n (js2-end-check node)))
+    ;; either it doesn't return a value in any branch...
+    (or (js2-flag-not-set-p n js2-END_RETURNS_VALUE)
+        ;; or it returns a value (or is unreached) at every branch
+        (js2-flag-not-set-p n (logior js2-END_DROPS_OFF
+                                      js2-END_RETURNS
+                                      js2-END_YIELDS)))))
+
+(defun js2-end-check-if (node)
+  &quot;Returns in the then and else blocks must be consistent with each other.
+If there is no else block, then the return statement can fall through.
+Returns logical OR of END_* flags&quot;
+  (let ((th (js2-if-node-then-part node))
+        (el (js2-if-node-else-part node)))
+    (if (null th)
+        js2-END_UNREACHED
+      (logior (js2-end-check th) (if el
+                                     (js2-end-check el)
+                                   js2-END_DROPS_OFF)))))
+
+(defun js2-end-check-switch (node)
+  &quot;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
+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.&quot;
+  (let ((rv js2-END_UNREACHED)
+        default-case)
+    ;; examine the cases
+    (catch 'break
+      (dolist (c (js2-switch-node-cases node))
+        (if (js2-case-node-expr c)
+            (js2-set-flag rv (js2-end-check-block c))
+          (setq default-case c)
+          (throw 'break nil))))
+    ;; we don't care how the cases drop into each other
+    (js2-clear-flag rv js2-END_DROPS_OFF)
+    ;; examine the default
+    (js2-set-flag rv (if default-case
+                         (js2-end-check default-case)
+                       js2-END_DROPS_OFF))
+    rv))
+
+(defun js2-end-check-try (node)
+ &quot;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
+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.&quot;
+ (let ((finally (js2-try-node-finally-block node))
+       rv)
+   ;; check the finally if it exists
+   (setq rv (if finally
+                (js2-end-check (js2-finally-node-body finally))
+              js2-END_DROPS_OFF))
+   ;; If the finally block always returns, then none of the returns
+   ;; in the try or catch blocks matter.
+   (when (js2-flag-set-p rv js2-END_DROPS_OFF)
+     (js2-clear-flag rv js2-END_DROPS_OFF)
+     ;; examine the try block
+     (js2-set-flag rv (js2-end-check (js2-try-node-try-block node)))
+     ;; check each catch block
+     (dolist (cb (js2-try-node-catch-clauses node))
+       (js2-set-flag rv (js2-end-check (js2-catch-node-block cb)))))
+   rv))
+
+(defun js2-end-check-loop (node)
+  &quot;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.
+
+    for(... ; ... ; ...) {}
+    for(... in ... ) {}
+    while(...) { }
+    do { } while(...)
+
+Returns logical OR of END_* flags.&quot;
+  (let ((rv (js2-end-check (js2-loop-node-body node)))
+        (condition (cond
+                    ((js2-while-node-p node)
+                     (js2-while-node-condition node))
+                     ((js2-do-node-p node)
+                      (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))
+        (js2-clear-flag rv js2-END_DROPS_OFF))
+
+    ;; look for effect of breaks
+    (js2-set-flag rv (js2-node-get-prop node
+                                        'CONTROL_BLOCK_PROP
+                                        js2-END_UNREACHED))
+    rv))
+
+(defun js2-end-check-block (node)
+  &quot;A general block of code is examined statement by statement.
+If any statement (even a compound one) returns in all branches, then
+subsequent statements are not examined.
+Returns logical OR of END_* flags.&quot;
+  (let* ((rv js2-END_DROPS_OFF)
+         (kids (js2-block-node-kids node))
+         (n (car kids)))
+    ;; Check each statment.  If the statement can continue onto the next
+    ;; one (i.e. END_DROPS_OFF is set), then check the next statement.
+    (while (and n (js2-flag-set-p rv js2-END_DROPS_OFF))
+      (js2-clear-flag rv js2-END_DROPS_OFF)
+      (js2-set-flag rv (js2-end-check n))
+      (setq kids (cdr kids)
+            n (car kids)))
+    rv))
+
+(defun js2-end-check-label (node)
+  &quot;A labeled statement implies that there may be a break to the label.
+The function processes the labeled statement and then checks the
+CONTROL_BLOCK_PROP property to see if there is ever a break to the
+particular label.
+Returns logical OR of END_* flags.&quot;
+  (let ((rv (js2-end-check (js2-labeled-stmt-node-stmt node))))
+    (logior rv (js2-node-get-prop node
+                                  'CONTROL_BLOCK_PROP
+                                  js2-END_UNREACHED))))
+
+(defun js2-end-check-break (node)
+  &quot;When a break is encountered annotate the statement being broken
+out of by setting its CONTROL_BLOCK_PROP property.
+Returns logical OR of END_* flags.&quot;
+  (and (js2-break-node-target node)
+       (js2-node-set-prop (js2-break-node-target node)
+                          'CONTROL_BLOCK_PROP
+                          js2-END_DROPS_OFF))
+  js2-END_UNREACHED)
+
+(defun js2-end-check (node)
+  &quot;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
+analysis will flag errors in cases where there may not be errors.
+Returns logical OR of END_* flags&quot;
+  (let (kid)
+    (cond
+     ((js2-break-node-p node)
+      (js2-end-check-break node))
+     ((js2-expr-stmt-node-p node)
+      (if (setq kid (js2-expr-stmt-node-expr node))
+          (js2-end-check kid)
+        js2-END_DROPS_OFF))
+     ((or (js2-continue-node-p node)
+          (js2-throw-node-p node))
+      js2-END_UNREACHED)
+     ((js2-return-node-p node)
+      (if (setq kid (js2-return-node-retval node))
+          js2-END_RETURNS_VALUE
+        js2-END_RETURNS))
+     ((js2-loop-node-p node)
+      (js2-end-check-loop node))
+     ((js2-switch-node-p node)
+      (js2-end-check-switch node))
+     ((js2-labeled-stmt-node-p node)
+      (js2-end-check-label node))
+     ((js2-if-node-p node)
+      (js2-end-check-if node))
+     ((js2-try-node-p node)
+      (js2-end-check-try node))
+     ((js2-block-node-p node)
+      (if (null (js2-block-node-kids node))
+          js2-END_DROPS_OFF
+        (js2-end-check-block node)))
+     ((js2-yield-node-p node)
+      js2-END_YIELDS)
+     (t
+      js2-END_DROPS_OFF))))
+
+(defun js2-always-defined-boolean-p (node)
+  &quot;Check if NODE always evaluates to true or false in boolean context.
+Returns 'ALWAYS_TRUE, 'ALWAYS_FALSE, or nil if it's neither always true
+nor always false.&quot;
+  (let ((tt (js2-node-type node))
+        num)
+    (cond
+     ((or (= tt js2-FALSE) (= tt js2-NULL))
+      'ALWAYS_FALSE)
+     ((= tt js2-TRUE)
+      'ALWAYS_TRUE)
+     ((= tt js2-NUMBER)
+      (setq num (js2-number-node-num-value node))
+      (if (and (not (eq num 0.0e+NaN))
+               (not (zerop num)))
+          'ALWAYS_TRUE
+        'ALWAYS_FALSE))
+     (t
+      nil))))
+
+;;; Scanner -- a port of Mozilla Rhino's lexer.
+;; Corresponds to Rhino files Token.java and TokenStream.java.
+
+(defvar js2-tokens nil
+  &quot;List of all defined token names.&quot;)  ; initialized in `js2-token-names'
+
+(defconst js2-token-names
+  (let* ((names (make-vector js2-num-tokens -1))
+         (case-fold-search nil)  ; only match js2-UPPER_CASE
+         (syms (apropos-internal &quot;^js2-\\(?:[A-Z_]+\\)&quot;)))
+    (loop for sym in syms
+          for i from 0
+          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. &quot;LET&quot;
+            (push sym js2-tokens)))
+    names)
+  &quot;Vector mapping int values to token string names, sans `js2-' prefix.&quot;)
+
+(defun js2-token-name (tok)
+  &quot;Return a string name for TOK, a token symbol or code.
+Signals an error if it's not a recognized token.&quot;
+  (let ((code tok))
+    (if (symbolp tok)
+        (setq code (symbol-value tok)))
+    (if (eq code -1)
+        &quot;ERROR&quot;
+      (if (and (numberp code)
+               (not (minusp code))
+               (&lt; code js2-num-tokens))
+          (aref js2-token-names code)
+        (error &quot;Invalid token: %s&quot; code)))))
+
+(defsubst js2-token-sym (tok)
+  &quot;Return symbol for TOK given its code, e.g. 'js2-LP for code 86.&quot;
+  (intern (js2-token-name tok)))
+
+(defconst js2-token-codes
+  (let ((table (make-hash-table :test 'eq :size 256)))
+    (loop for name across js2-token-names
+          for sym = (intern (concat &quot;js2-&quot; name))
+          do
+          (puthash sym (symbol-value sym) table))
+    ;; clean up a few that are &quot;wrong&quot; in Rhino's token codes
+    (puthash 'js2-DELETE js2-DELPROP table)
+    table)
+  &quot;Hashtable mapping token symbols to their bytecodes.&quot;)
+
+(defsubst js2-token-code (sym)
+  &quot;Return code for token symbol SYM, e.g. 86 for 'js2-LP.&quot;
+  (or (gethash sym js2-token-codes)
+      (error &quot;Invalid token symbol: %s &quot; sym)))  ; signal code bug
+
+(defsubst js2-report-scan-error (msg &amp;optional no-throw beg len)
+  (setq js2-token-end js2-ts-cursor)
+  (js2-report-error msg nil
+                    (or beg js2-token-beg)
+                    (or len (- js2-token-end js2-token-beg)))
+  (unless no-throw
+    (throw 'return js2-ERROR)))
+
+(defsubst js2-get-string-from-buffer ()
+  &quot;Reverse the char accumulator and return it as a string.&quot;
+  (setq js2-token-end js2-ts-cursor)
+  (if js2-ts-string-buffer
+      (apply #'string (nreverse js2-ts-string-buffer))
+    &quot;&quot;))
+
+;; TODO:  could potentially avoid a lot of consing by allocating a
+;; char buffer the way Rhino does.
+(defsubst js2-add-to-string (c)
+  (push c js2-ts-string-buffer))
+
+;; Note that when we &quot;read&quot; the end-of-file, we advance js2-ts-cursor
+;; to (1+ (point-max)), which lets the scanner treat end-of-file like
+;; any other character:  when it's not part of the current token, we
+;; unget it, allowing it to be read again by the following call.
+(defsubst js2-unget-char ()
+  (decf js2-ts-cursor))
+
+;; 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 ()
+  &quot;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.
+Returns `js2-EOF_CHAR' if we hit the end of the buffer.
+Also updates `js2-ts-hit-eof' and `js2-ts-line-start' as needed.&quot;
+  (let (c)
+    ;; check for end of buffer
+    (if (&gt;= js2-ts-cursor (point-max))
+        (setq js2-ts-hit-eof t
+              js2-ts-cursor (1+ js2-ts-cursor)
+              c js2-EOF_CHAR)  ; return value
+      ;; otherwise read next char
+      (setq c (char-before (incf js2-ts-cursor)))
+      ;; if we read a newline, update counters
+      (if (= c ?\n)
+          (setq js2-ts-line-start js2-ts-cursor
+                js2-ts-lineno (1+ js2-ts-lineno)))
+      ;; TODO:  skip over format characters
+      c)))
+
+(defsubst js2-read-unicode-escape ()
+  &quot;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.
+Doesn't change the values of any scanner variables.&quot;
+  ;; I really wish I knew a better way to do this, but I can't
+  ;; find the Emacs function that takes a 16-bit int and converts
+  ;; it to a Unicode/utf-8 character.  So I basically eval it with (read).
+  ;; Have to first check that it's 4 hex characters or it may stop
+  ;; the read early.
+  (ignore-errors
+    (let ((s (buffer-substring-no-properties js2-ts-cursor
+                                             (+ 4 js2-ts-cursor))))
+      (if (string-match &quot;[a-zA-Z0-9]\\{4\\}&quot; s)
+          (read (concat &quot;?\\u&quot; s))))))
+
+(defsubst js2-match-char (test)
+  &quot;Consume and return next character if it matches TEST, a character.
+Returns nil and consumes nothing if TEST is not the next character.&quot;
+  (let ((c (js2-get-char)))
+    (if (eq c test)
+        t
+      (js2-unget-char)
+      nil)))
+
+(defsubst js2-peek-char ()
+  (prog1
+      (js2-get-char)
+    (js2-unget-char)))
+
+(defsubst 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)
+  &quot;Implementation of java.lang.Character.isJavaIdentifierPart()&quot;
+  ;; TODO:  make me Unicode-friendly.  See comments above.
+  (or
+   (memq c '(?$ ?_))
+   (js2-char-uppercase-p c)
+   (js2-char-lowercase-p c)
+   (and (&gt;= c ?0) (&lt;= c ?9))))
+
+(defsubst js2-alpha-p (c)
+  (cond ((and (&lt;= ?A c) (&lt;= c ?Z)) t)
+        ((and (&lt;= ?a c) (&lt;= c ?z)) t)
+        (t nil)))
+
+(defsubst js2-digit-p (c)
+  (and (&lt;= ?0 c) (&lt;= c ?9)))
+
+(defsubst js2-js-space-p (c)
+  (if (&lt;= c 127)
+      (memq c '(#x20 #x9 #xB #xC #xD))
+    (or
+     (eq c #xA0)
+     ;; TODO:  change this nil to check for Unicode space character
+     nil)))
+
+(defconst js2-eol-chars (list js2-EOF_CHAR ?\n ?\r))
+
+(defsubst js2-skip-line ()
+  &quot;Skip to end of line&quot;
+  (let (c)
+    (while (not (memq (setq c (js2-get-char)) js2-eol-chars)))
+    (js2-unget-char)
+    (setq js2-token-end js2-ts-cursor)))
+
+(defun js2-init-scanner (&amp;optional buf line)
+  &quot;Create token stream for BUF starting on LINE.
+BUF defaults to current-buffer and line defaults to 1.
+
+A buffer can only have one scanner active at a time, which yields
+dramatically simpler code than using a defstruct.  If you need to
+have simultaneous scanners in a buffer, copy the regions to scan
+into temp buffers.&quot;
+  (save-excursion
+    (when buf
+      (set-buffer buf))
+    (setq js2-ts-dirty-line nil
+          js2-ts-regexp-flags nil
+          js2-ts-string &quot;&quot;
+          js2-ts-number nil
+          js2-ts-hit-eof nil
+          js2-ts-line-start 0
+          js2-ts-lineno (or line 1)
+          js2-ts-line-end-char -1
+          js2-ts-cursor (point-min)
+          js2-ts-is-xml-attribute nil
+          js2-ts-xml-is-tag-content nil
+          js2-ts-xml-open-tags-count 0
+          js2-ts-string-buffer nil)))
+
+;; This function uses the cached op, string and number fields in
+;; TokenStream; if getToken has been called since the passed token
+;; was scanned, the op or string printed may be incorrect.
+(defun js2-token-to-string (token)
+  ;; Not sure where this function is used in Rhino.  Not tested.
+  (if (not js2-debug-print-trees)
+      &quot;&quot;
+    (let ((name (js2-token-name token)))
+      (cond
+       ((memq token (list js2-STRING js2-REGEXP js2-NAME))
+        (concat name &quot; `&quot; js2-ts-string &quot;'&quot;))
+       ((eq token js2-NUMBER)
+        (format &quot;NUMBER %g&quot; js2-ts-number))
+       (t
+        name)))))
+
+(defconst js2-keywords
+  '(break
+    case catch const continue
+    debugger default delete do
+    else enum
+    false finally for function
+    if in instanceof import
+    let
+    new null
+    return
+    switch
+    this throw true try typeof
+    var void
+    while with
+    yield))
+
+;; Token names aren't exactly the same as the keywords, unfortunately.
+;; E.g. enum isn't in the tokens, and delete is js2-DELPROP.
+(defconst js2-kwd-tokens
+  (let ((table (make-vector js2-num-tokens nil))
+        (tokens
+         (list js2-BREAK
+               js2-CASE js2-CATCH js2-CONST js2-CONTINUE
+               js2-DEBUGGER js2-DEFAULT js2-DELPROP js2-DO
+               js2-ELSE
+               js2-FALSE js2-FINALLY js2-FOR js2-FUNCTION
+               js2-IF js2-IN js2-INSTANCEOF js2-IMPORT
+               js2-LET
+               js2-NEW js2-NULL
+               js2-RETURN
+               js2-SWITCH
+               js2-THIS js2-THROW js2-TRUE js2-TRY js2-TYPEOF
+               js2-VAR
+               js2-WHILE js2-WITH
+               js2-YIELD)))
+    (dolist (i tokens)
+      (aset table i 'font-lock-keyword-face))
+    (aset table js2-STRING 'font-lock-string-face)
+    (aset table js2-REGEXP 'font-lock-string-face)
+    (aset table js2-COMMENT 'font-lock-comment-face)
+    (aset table js2-THIS 'font-lock-builtin-face)
+    (aset table js2-VOID 'font-lock-constant-face)
+    (aset table js2-NULL 'font-lock-constant-face)
+    (aset table js2-TRUE 'font-lock-constant-face)
+    (aset table js2-FALSE 'font-lock-constant-face)
+    table)
+  &quot;Vector whose values are non-nil for tokens that are keywords.
+The values are default faces to use for highlighting the keywords.&quot;)
+
+(defconst js2-reserved-words
+  '(abstract
+    boolean byte
+    char class
+    double
+    enum export extends
+    final float
+    goto
+    implements import int interface
+    long
+    native
+    package private protected public
+    short static super synchronized
+    throws transient
+    volatile))
+
+(defconst js2-keyword-names
+  (let ((table (make-hash-table :test 'equal)))
+    (loop for k in js2-keywords
+          do (puthash
+              (symbol-name k)                            ; instanceof
+              (intern (concat &quot;js2-&quot;
+                              (upcase (symbol-name k)))) ; js2-INSTANCEOF
+              table))
+    table)
+  &quot;JavaScript keywords by name, mapped to their symbols.&quot;)
+
+(defconst js2-reserved-word-names
+  (let ((table (make-hash-table :test 'equal)))
+    (loop for k in js2-reserved-words
+          do
+          (puthash (symbol-name k) 'js2-RESERVED table))
+    table)
+  &quot;JavaScript reserved words by name, mapped to 'js2-RESERVED.&quot;)
+
+(defsubst js2-collect-string (buf)
+  &quot;Convert BUF, a list of chars, to a string.
+Reverses BUF before converting.&quot;
+  (cond
+   ((stringp buf)
+    buf)
+   ((null buf)  ; for emacs21 compat
+    &quot;&quot;)
+   (t
+    (if buf
+        (apply #'string (nreverse buf))
+      &quot;&quot;))))
+
+(defun js2-string-to-keyword (s)
+  &quot;Return token for S, a string, if S is a keyword or reserved word.
+Returns a symbol such as 'js2-BREAK, or nil if not keyword/reserved.&quot;
+  (or (gethash s js2-keyword-names)
+      (gethash s js2-reserved-word-names)))
+
+(defsubst js2-ts-set-char-token-bounds ()
+  &quot;Used when next token is one character.&quot;
+  (setq js2-token-beg (1- js2-ts-cursor)
+        js2-token-end js2-ts-cursor))
+
+(defsubst js2-ts-return (token)
+  &quot;Return an N-character TOKEN from `js2-get-token'.
+Updates `js2-token-end' accordingly.&quot;
+  (setq js2-token-end js2-ts-cursor)
+  (throw 'return token))
+
+(defsubst js2-x-digit-to-int (c accumulator)
+  &quot;Build up a hex number.
+If C is a hexadecimal digit, return ACCUMULATOR * 16 plus
+corresponding number.  Otherwise return -1.&quot;
+  (catch 'return
+    (catch 'check
+      ;; Use 0..9 &lt; A..Z &lt; a..z
+      (cond
+       ((&lt;= c ?9)
+        (decf c ?0)
+        (if (&lt;= 0 c)
+            (throw 'check nil)))
+       ((&lt;= c ?F)
+        (when (&lt;= ?A c)
+          (decf c (- ?A 10))
+          (throw 'check nil)))
+       ((&lt;= c ?f)
+        (when (&lt;= ?a c)
+          (decf c (- ?a 10))
+          (throw 'check nil))))
+      (throw 'return -1))
+    (logior c (lsh accumulator 4))))
+
+(defun js2-get-token ()
+  &quot;Return next JavaScript token, an int such as js2-RETURN.&quot;
+  (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.
+        (setq continue t)
+        (while continue
+          (setq c (js2-get-char))
+          (cond
+           ((eq c js2-EOF_CHAR)
+            (js2-ts-set-char-token-bounds)
+            (throw 'return js2-EOF))
+           ((eq c ?\n)
+            (js2-ts-set-char-token-bounds)
+            (setq js2-ts-dirty-line nil)
+            (throw 'return js2-EOL))
+           ((not (js2-js-space-p c))
+            (if (/= c ?-)               ; in case end of HTML comment
+                (setq js2-ts-dirty-line t))
+            (setq continue nil))))
+        ;; Assume the token will be 1 char - fixed up below.
+        (js2-ts-set-char-token-bounds)
+        (when (eq c ?@)
+          (throw 'return js2-XMLATTR))
+        ;; identifier/keyword/instanceof?
+        ;; watch out for starting with a &lt;backslash&gt;
+        (cond
+         ((eq c ?\\)
+          (setq c (js2-get-char))
+          (if (eq c ?u)
+              (setq identifier-start t
+                    is-unicode-escape-start t
+                    js2-ts-string-buffer nil)
+            (setq identifier-start nil)
+            (js2-unget-char)
+            (setq c ?\\)))
+         (t
+          (when (setq identifier-start (js2-java-identifier-start-p c))
+            (setq js2-ts-string-buffer nil)
+            (js2-add-to-string c))))
+        (when identifier-start
+          (setq contains-escape is-unicode-escape-start)
+          (catch 'break
+            (while t
+              (if is-unicode-escape-start
+                  ;; strictly speaking we should probably push-back
+                  ;; all the bad characters if the &lt;backslash&gt;uXXXX
+                  ;; sequence is malformed. But since there isn't a
+                  ;; correct context(is there?) for a bad Unicode
+                  ;; escape sequence in an identifier, we can report
+                  ;; an error here.
+                  (progn
+                    (setq escape-val 0)
+                    (dotimes (i 4)
+                      (setq c (js2-get-char)
+                            escape-val (js2-x-digit-to-int c escape-val))
+                      ;; Next check takes care of c &lt; 0 and bad escape
+                      (if (minusp escape-val)
+                          (throw 'break nil)))
+                    (if (minusp escape-val)
+                        (js2-report-scan-error &quot;msg.invalid.escape&quot; t))
+                    (js2-add-to-string escape-val)
+                    (setq is-unicode-escape-start nil))
+                (setq c (js2-get-char))
+                (cond
+                 ((eq c ?\\)
+                  (setq c (js2-get-char))
+                  (if (eq c ?u)
+                      (setq is-unicode-escape-start t
+                            contains-escape t)
+                    (js2-report-scan-error &quot;msg.illegal.character&quot; t)))
+                 (t
+                  (if (or (eq c js2-EOF_CHAR)
+                          (not (js2-java-identifier-part-p c)))
+                      (throw 'break nil))
+                  (js2-add-to-string c))))))
+          (js2-unget-char)
+          (setq str (js2-get-string-from-buffer))
+          (unless contains-escape
+            ;; OPT we shouldn't have to make a string (object!) to
+            ;; check if it's a keyword.
+            ;; Return the corresponding token if it's a keyword
+            (when (setq result (js2-string-to-keyword str))
+              (if (and (&lt; js2-language-version 170)
+                       (memq result '(js2-LET js2-YIELD)))
+                  ;; LET and YIELD are tokens only in 1.7 and later
+                  (setq result 'js2-NAME))
+              (if (not (eq result js2-RESERVED))
+                  (throw 'return (js2-token-code result)))
+              (js2-report-warning &quot;msg.reserved.keyword&quot; str)))
+          ;; If we want to intern these as Rhino does, just use (intern str)
+          (setq js2-ts-string str)
+          (throw 'return js2-NAME))     ; end identifier/kwd check
+        ;; is it a number?
+        (when (or (js2-digit-p c)
+                  (and (eq c ?.) (js2-digit-p (js2-peek-char))))
+          (setq js2-ts-string-buffer nil
+                base 10)
+          (when (eq c ?0)
+            (setq c (js2-get-char))
+            (cond
+             ((or (eq c ?x) (eq c ?X))
+              (setq base 16)
+              (setq c (js2-get-char)))
+             ((js2-digit-p c)
+              (setq base 8))
+             (t
+              (js2-add-to-string ?0))))
+          (if (eq base 16)
+              (while (&lt;= 0 (js2-x-digit-to-int c 0))
+                (js2-add-to-string c)
+                (setq c (js2-get-char)))
+            (while (and (&lt;= ?0 c) (&lt;= c ?9))
+              ;; We permit 08 and 09 as decimal numbers, which
+              ;; makes our behavior a superset of the ECMA
+              ;; numeric grammar.  We might not always be so
+              ;; permissive, so we warn about it.
+              (when (and (eq base 8) (&gt;= c ?8))
+                (js2-report-warning &quot;msg.bad.octal.literal&quot;
+                                    (if (eq c ?8) &quot;8&quot; &quot;9&quot;))
+                (setq base 10))
+              (js2-add-to-string c)
+              (setq c (js2-get-char))))
+          (setq is-integer t)
+          (when (and (eq base 10) (memq c '(?. ?e ?E)))
+            (setq is-integer nil)
+            (when (eq c ?.)
+              (loop do
+                    (js2-add-to-string c)
+                    (setq c (js2-get-char))
+                    while (js2-digit-p c)))
+            (when (memq c '(?e ?E))
+              (js2-add-to-string c)
+              (setq c (js2-get-char))
+              (when (memq c '(?+ ?-))
+                (js2-add-to-string c)
+                (setq c (js2-get-char)))
+              (unless (js2-digit-p c)
+                (js2-report-scan-error &quot;msg.missing.exponent&quot; t))
+              (loop do
+                    (js2-add-to-string c)
+                    (setq c (js2-get-char))
+                    while (js2-digit-p c))))
+          (js2-unget-char)
+          (setq js2-ts-string (js2-get-string-from-buffer)
+                js2-ts-number
+                (if (and (eq base 10) (not is-integer))
+                    (string-to-number js2-ts-string)
+                  ;; TODO:  call runtime number-parser.  Some of it is in
+                  ;; js2-util.el, but I need to port ScriptRuntime.stringToNumber.
+                  (string-to-number js2-ts-string)))
+          (throw 'return js2-NUMBER))
+        ;; is it a string?
+        (when (memq c '(?\&quot; ?\'))
+          ;; We attempt to accumulate a string the fast way, by
+          ;; building it directly out of the reader.  But if there
+          ;; are any escaped characters in the string, we revert to
+          ;; building it out of a string buffer.
+          (setq quote-char c
+                js2-ts-string-buffer nil
+                c (js2-get-char))
+          (catch 'break
+            (while (/= c quote-char)
+              (catch 'continue
+                (when (or (eq c ?\n) (eq c js2-EOF_CHAR))
+                  (js2-unget-char)
+                  (setq js2-token-end js2-ts-cursor)
+                  (js2-report-error &quot;msg.unterminated.string.lit&quot;)
+                  (throw 'return js2-STRING))
+                (when (eq c ?\\)
+                  ;; We've hit an escaped character
+                  (setq c (js2-get-char))
+                  (case c
+                    (?b (setq c ?\b))
+                    (?f (setq c ?\f))
+                    (?n (setq c ?\n))
+                    (?r (setq c ?\r))
+                    (?t (setq c ?\t))
+                    (?v (setq c ?\v))
+                    (?u
+                     (setq c1 (js2-read-unicode-escape))
+                     (if js2-parse-ide-mode
+                         (if c1
+                             (progn
+                               ;; just copy the string in IDE-mode
+                               (js2-add-to-string ?\\)
+                               (js2-add-to-string ?u)
+                               (dotimes (i 3)
+                                 (js2-add-to-string (js2-get-char)))
+                               (setq c (js2-get-char))) ; added at end of loop
+                           ;; flag it as an invalid escape
+                           (js2-report-warning &quot;msg.invalid.escape&quot;
+                                               nil (- js2-ts-cursor 2) 6))
+                       ;; Get 4 hex digits; if the u escape is not
+                       ;; followed by 4 hex digits, use 'u' + the
+                       ;; literal character sequence that follows.
+                       (js2-add-to-string ?u)
+                       (setq escape-val 0)
+                       (dotimes (i 4)
+                         (setq c (js2-get-char)
+                               escape-val (js2-x-digit-to-int c escape-val))
+                         (if (minusp escape-val)
+                             (throw 'continue nil))
+                         (js2-add-to-string c))
+                       ;; prepare for replace of stored 'u' sequence by escape value
+                       (setq js2-ts-string-buffer (nthcdr 5 js2-ts-string-buffer)
+                             c escape-val)))
+                    (?x
+                     ;; Get 2 hex digits, defaulting to 'x'+literal
+                     ;; sequence, as above.
+                     (setq c (js2-get-char)
+                           escape-val (js2-x-digit-to-int c 0))
+                     (if (minusp escape-val)
+                         (progn
+                           (js2-add-to-string ?x)
+                           (throw 'continue nil))
+                       (setq c1 c
+                             c (js2-get-char)
+                             escape-val (js2-x-digit-to-int c escape-val))
+                       (if (minusp escape-val)
+                           (progn
+                             (js2-add-to-string ?x)
+                             (js2-add-to-string c1)
+                             (throw 'continue nil))
+                         ;; got 2 hex digits
+                         (setq c escape-val))))
+                    (?\n
+                     ;; Remove line terminator after escape to follow
+                     ;; SpiderMonkey and C/C++
+                     (setq c (js2-get-char))
+                     (throw 'continue nil))
+                    (t
+                     (when (and (&lt;= ?0 c) (&lt; c ?8))
+                       (setq val (- c ?0)
+                             c (js2-get-char))
+                       (when (and (&lt;= ?0 c) (&lt; c ?8))
+                         (setq val (- (+ (* 8 val) c) ?0)
+                               c (js2-get-char))
+                         (when (and (&lt;= ?0 c)
+                                    (&lt; c ?8)
+                                    (&lt; val #o37))
+                           ;; c is 3rd char of octal sequence only
+                           ;; if the resulting val &lt;= 0377
+                           (setq val (- (+ (* 8 val) c) ?0)
+                                 c (js2-get-char))))
+                       (js2-unget-char)
+                       (setq c val)))))
+                (js2-add-to-string c)
+                (setq c (js2-get-char)))))
+          (setq js2-ts-string (js2-get-string-from-buffer))
+          (throw 'return js2-STRING))
+        (case c
+          (?\;
+           (throw 'return js2-SEMI))
+          (?\[
+           (throw 'return js2-LB))
+          (?\]
+           (throw 'return js2-RB))
+          (?{
+           (throw 'return js2-LC))
+          (?}
+           (throw 'return js2-RC))
+          (?\(
+           (throw 'return js2-LP))
+          (?\)
+           (throw 'return js2-RP))
+          (?,
+           (throw 'return js2-COMMA))
+          (??
+           (throw 'return js2-HOOK))
+          (?:
+           (if (js2-match-char ?:)
+               (js2-ts-return js2-COLONCOLON)
+             (throw 'return js2-COLON)))
+          (?.
+           (if (js2-match-char ?.)
+               (js2-ts-return js2-DOTDOT)
+             (if (js2-match-char ?\()
+                 (js2-ts-return js2-DOTQUERY)
+               (throw 'return js2-DOT))))
+          (?|
+           (if (js2-match-char ?|)
+               (throw 'return js2-OR)
+             (if (js2-match-char ?=)
+                 (js2-ts-return js2-ASSIGN_BITOR)
+               (throw 'return js2-BITOR))))
+          (?^
+           (if (js2-match-char ?=)
+               (js2-ts-return js2-ASSIGN_BITOR)
+             (throw 'return js2-BITXOR)))
+          (?&amp;
+           (if (js2-match-char ?&amp;)
+               (throw 'return js2-AND)
+             (if (js2-match-char ?=)
+                 (js2-ts-return js2-ASSIGN_BITAND)
+               (throw 'return js2-BITAND))))
+          (?=
+           (if (js2-match-char ?=)
+               (if (js2-match-char ?=)
+                   (js2-ts-return js2-SHEQ)
+                 (throw 'return js2-EQ))
+             (throw 'return js2-ASSIGN)))
+          (?!
+           (if (js2-match-char ?=)
+               (if (js2-match-char ?=)
+                   (js2-ts-return js2-SHNE)
+                 (js2-ts-return js2-NE))
+             (throw 'return js2-NOT)))
+          (?&lt;
+           ;; NB:treat HTML begin-comment as comment-till-eol
+           (when (js2-match-char ?!)
+             (when (js2-match-char ?-)
+               (when (js2-match-char ?-)
+                 (js2-skip-line)
+                 (setq js2-ts-comment-type 'html)
+                 (throw 'return js2-COMMENT)))
+             (js2-unget-char))
+           (if (js2-match-char ?&lt;)
+               (if (js2-match-char ?=)
+                   (js2-ts-return js2-ASSIGN_LSH)
+                 (js2-ts-return js2-LSH))
+             (if (js2-match-char ?=)
+                 (js2-ts-return js2-LE)
+               (throw 'return js2-LT))))
+          (?&gt;
+           (if (js2-match-char ?&gt;)
+               (if (js2-match-char ?&gt;)
+                   (if (js2-match-char ?=)
+                       (js2-ts-return js2-ASSIGN_URSH)
+                     (js2-ts-return js2-URSH))
+                 (if (js2-match-char ?=)
+                     (js2-ts-return js2-ASSIGN_RSH)
+                   (js2-ts-return js2-RSH)))
+             (if (js2-match-char ?=)
+                 (js2-ts-return js2-GE)
+               (throw 'return js2-GT))))
+          (?*
+           (if (js2-match-char ?=)
+               (js2-ts-return js2-ASSIGN_MUL)
+             (throw 'return js2-MUL)))
+          (?/
+           ;; is it a // comment?
+           (when (js2-match-char ?/)
+             (setq js2-token-beg (- js2-ts-cursor 2))
+             (js2-skip-line)
+             (setq js2-ts-comment-type 'line)
+             ;; include newline so highlighting goes to end of window
+             (incf js2-token-end)
+             (throw 'return js2-COMMENT))
+           ;; is it a /* comment?
+           (when (js2-match-char ?*)
+             (setq look-for-slash nil
+                   js2-token-beg (- js2-ts-cursor 2)
+                   js2-ts-comment-type
+                   (if (js2-match-char ?*)
+                       (progn
+                         (setq look-for-slash t)
+                         'jsdoc)
+                     'block))
+             (while t
+               (setq c (js2-get-char))
+               (cond
+                ((eq c js2-EOF_CHAR)
+                 (setq js2-token-end (1- js2-ts-cursor))
+                 (js2-report-error &quot;msg.unterminated.comment&quot;)
+                 (throw 'return js2-COMMENT))
+                ((eq c ?*)
+                 (setq look-for-slash t))
+                ((eq c ?/)
+                 (if look-for-slash
+                   (js2-ts-return js2-COMMENT)))
+                (t
+                 (setq look-for-slash nil
+                       js2-token-end js2-ts-cursor)))))
+           (if (js2-match-char ?=)
+               (js2-ts-return js2-ASSIGN_DIV)
+             (throw 'return js2-DIV)))
+           (?#
+            (when js2-skip-preprocessor-directives
+              (js2-skip-line)
+              (setq js2-ts-comment-type 'preprocessor
+                    js2-token-end js2-ts-cursor)
+              (throw 'return js2-COMMENT))
+            (throw 'return js2-ERROR))
+          (?%
+           (if (js2-match-char ?=)
+               (js2-ts-return js2-ASSIGN_MOD)
+             (throw 'return js2-MOD)))
+          (?~
+           (throw 'return js2-BITNOT))
+          (?+
+           (if (js2-match-char ?=)
+               (js2-ts-return js2-ASSIGN_ADD)
+             (if (js2-match-char ?+)
+                 (js2-ts-return js2-INC)
+               (throw 'return js2-ADD))))
+          (?-
+           (cond
+            ((js2-match-char ?=)
+             (setq c js2-ASSIGN_SUB))
+            ((js2-match-char ?-)
+             (unless js2-ts-dirty-line
+               ;; treat HTML end-comment after possible whitespace
+               ;; after line start as comment-until-eol
+               (when (js2-match-char ?&gt;)
+                 (js2-skip-line)
+                 (setq js2-ts-comment-type 'html)
+                 (throw 'return js2-COMMENT)))
+             (setq c js2-DEC))
+            (t
+             (setq c js2-SUB)))
+           (setq js2-ts-dirty-line t)
+           (js2-ts-return c))
+          (otherwise
+           (js2-report-scan-error &quot;msg.illegal.character&quot;)))))))
+
+(defun js2-read-regexp (start-token)
+  &quot;Called by parser when it gets / or /= in literal context.&quot;
+  (let (c
+        err
+        in-class  ; inside a '[' .. ']' character-class
+        flags
+        (continue t))
+    (setq js2-token-beg js2-ts-cursor
+          js2-ts-string-buffer nil
+          js2-ts-regexp-flags nil)
+    (if (eq start-token js2-ASSIGN_DIV)
+        ;; mis-scanned /=
+        (js2-add-to-string ?=)
+      (if (not (eq start-token js2-DIV))
+          (error &quot;failed assertion&quot;)))
+    (while (and (not err)
+                (or (/= (setq c (js2-get-char)) ?/)
+                    in-class))
+      (cond
+       ((or (= c ?\n)
+            (= c js2-EOF_CHAR))
+        (setq js2-token-end (1- js2-ts-cursor)
+              err t
+              js2-ts-string (js2-collect-string js2-ts-string-buffer))
+        (js2-report-error &quot;msg.unterminated.re.lit&quot;))
+       (t (cond
+           ((= c ?\\)
+            (js2-add-to-string c)
+            (setq c (js2-get-char)))
+           ((= c ?\[)
+            (setq in-class t))
+           ((= c ?\])
+            (setq in-class nil)))
+          (js2-add-to-string c))))
+    (unless err
+      (while continue
+        (cond
+         ((js2-match-char ?g)
+          (push ?g flags))
+         ((js2-match-char ?i)
+          (push ?i flags))
+         ((js2-match-char ?m)
+          (push ?m flags))
+         (t
+          (setq continue nil))))
+      (if (js2-alpha-p (js2-peek-char))
+          (js2-report-scan-error &quot;msg.invalid.re.flag&quot; t
+                                 js2-ts-cursor 1))
+      (setq js2-ts-string (js2-collect-string js2-ts-string-buffer)
+            js2-ts-regexp-flags (js2-collect-string flags)
+            js2-token-end js2-ts-cursor)
+      ;; tell `parse-partial-sexp' to ignore this range of chars
+      (put-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
+        js2-ts-is-xml-attribute nil
+        js2-ts-xml-is-tag-content nil)
+  (js2-unget-char)
+  (js2-get-next-xml-token))
+
+(defsubst js2-xml-discard-string ()
+  &quot;Throw away the string in progress and flag an XML parse error.&quot;
+  (setq js2-ts-string-buffer nil
+        js2-ts-string nil)
+  (js2-report-scan-error &quot;msg.XML.bad.form&quot; t))
+
+(defun js2-get-next-xml-token ()
+  (setq js2-ts-string-buffer nil  ; for recording the XML
+        js2-token-beg js2-ts-cursor)
+  (let (c result)
+    (setq result
+          (catch 'return
+            (while t
+              (setq c (js2-get-char))
+              (cond
+               ((= c js2-EOF_CHAR)
+                (throw 'return js2-ERROR))
+               (js2-ts-xml-is-tag-content
+                (case c
+                  (?&gt;
+                   (js2-add-to-string c)
+                   (setq js2-ts-xml-is-tag-content nil
+                         js2-ts-is-xml-attribute nil))
+                  (?/
+                   (js2-add-to-string c)
+                   (when (eq ?&gt; (js2-peek-char))
+                     (setq c (js2-get-char))
+                     (js2-add-to-string c)
+                     (setq js2-ts-xml-is-tag-content nil)
+                     (decf js2-ts-xml-open-tags-count)))
+                  (?{
+                   (js2-unget-char)
+                   (setq js2-ts-string (js2-get-string-from-buffer))
+                   (throw 'return js2-XML))
+                  ((?\' ?\&quot;)
+                   (js2-add-to-string c)
+                   (unless (js2-read-quoted-string c)
+                     (throw 'return js2-ERROR)))
+                  (?=
+                   (js2-add-to-string c)
+                   (setq js2-ts-is-xml-attribute t))
+                  ((? ?\t ?\r ?\n)
+                   (js2-add-to-string c))
+                  (t
+                   (js2-add-to-string c)
+                   (setq js2-ts-is-xml-attribute nil)))
+                (when (and (not js2-ts-xml-is-tag-content)
+                           (zerop js2-ts-xml-open-tags-count))
+                  (setq js2-ts-string (js2-get-string-from-buffer))
+                  (throw 'return js2-XMLEND)))
+               (t
+                ;; else not tag content
+                (case c
+                  (?&lt;
+                   (js2-add-to-string c)
+                   (setq c (js2-peek-char))
+                   (case c
+                     (?!
+                      (setq c (js2-get-char)) ;; skip !
+                      (js2-add-to-string c)
+                      (setq c (js2-peek-char))
+                      (case c
+                        (?-
+                         (setq c (js2-get-char)) ;; skip -
+                         (js2-add-to-string c)
+                         (if (eq c ?-)
+                             (progn
+                               (js2-add-to-string c)
+                               (unless (js2-read-xml-comment)
+                                 (throw 'return js2-ERROR)))
+                           (js2-xml-discard-string)
+                           (throw 'return js2-ERROR)))
+                        (?\[
+                         (setq c (js2-get-char)) ;; skip [
+                         (js2-add-to-string c)
+                         (if (and (= (js2-get-char) ?C)
+                                  (= (js2-get-char) ?D)
+                                  (= (js2-get-char) ?A)
+                                  (= (js2-get-char) ?T)
+                                  (= (js2-get-char) ?A)
+                                  (= (js2-get-char) ?\[))
+                             (progn
+                               (js2-add-to-string ?C)
+                               (js2-add-to-string ?D)
+                               (js2-add-to-string ?A)
+                               (js2-add-to-string ?T)
+                               (js2-add-to-string ?A)
+                               (js2-add-to-string ?\[)
+                               (unless (js2-read-cdata)
+                                 (throw 'return js2-ERROR)))
+                           (js2-xml-discard-string)
+                           (throw 'return js2-ERROR)))
+                        (t
+                         (unless (js2-read-entity)
+                           (throw 'return js2-ERROR)))))
+                     (??
+                      (setq c (js2-get-char)) ;; skip ?
+                      (js2-add-to-string c)
+                      (unless (js2-read-PI)
+                        (throw 'return js2-ERROR)))
+                     (?/
+                      ;; end tag
+                      (setq c (js2-get-char)) ;; skip /
+                      (js2-add-to-string c)
+                      (when (zerop js2-ts-xml-open-tags-count)
+                        (js2-xml-discard-string)
+                        (throw 'return js2-ERROR))
+                      (setq js2-ts-xml-is-tag-content t)
+                      (decf js2-ts-xml-open-tags-count))
+                     (t
+                      ;; start tag
+                      (setq js2-ts-xml-is-tag-content t)
+                      (incf js2-ts-xml-open-tags-count))))
+                  (?{
+                   (js2-unget-char)
+                   (setq js2-ts-string (js2-get-string-from-buffer))
+                   (throw 'return js2-XML))
+                  (t
+                   (js2-add-to-string c))))))))
+    (setq js2-token-end js2-ts-cursor)
+    result))
+
+(defun js2-read-quoted-string (quote)
+  (let (c)
+    (catch 'return
+      (while (/= (setq c (js2-get-char)) js2-EOF_CHAR)
+        (js2-add-to-string c)
+        (if (eq c quote)
+            (throw 'return t)))
+      (js2-xml-discard-string)  ;; throw away string in progress
+      nil)))
+
+(defun js2-read-xml-comment ()
+  (let ((c (js2-get-char)))
+    (catch 'return
+      (while (/= c js2-EOF_CHAR)
+        (catch 'continue
+          (js2-add-to-string c)
+          (when (and (eq c ?-) (eq ?- (js2-peek-char)))
+            (setq c (js2-get-char))
+            (js2-add-to-string c)
+            (if (eq (js2-peek-char) ?&gt;)
+                (progn
+                  (setq c (js2-get-char)) ;; skip &gt;
+                  (js2-add-to-string c)
+                  (throw 'return t))
+              (throw 'continue nil)))
+          (setq c (js2-get-char))))
+      (js2-xml-discard-string)
+      nil)))
+
+(defun js2-read-cdata ()
+  (let ((c (js2-get-char)))
+    (catch 'return
+      (while (/= c js2-EOF_CHAR)
+        (catch 'continue
+          (js2-add-to-string c)
+          (when (and (eq c ?\]) (eq (js2-peek-char) ?\]))
+            (setq c (js2-get-char))
+            (js2-add-to-string c)
+            (if (eq (js2-peek-char) ?&gt;)
+                (progn
+                  (setq c (js2-get-char)) ;; Skip &gt;
+                  (js2-add-to-string c)
+                  (throw 'return t))
+              (throw 'continue nil)))
+          (setq c (js2-get-char))))
+      (js2-xml-discard-string)
+      nil)))
+
+(defun js2-read-entity ()
+  (let ((decl-tags 1)
+        c)
+    (catch 'return
+      (while (/= js2-EOF_CHAR (setq c (js2-get-char)))
+        (js2-add-to-string c)
+        (case c
+          (?&lt;
+           (incf decl-tags))
+          (?&gt;
+           (decf decl-tags)
+           (if (zerop decl-tags)
+               (throw 'return t)))))
+      (js2-xml-discard-string)
+      nil)))
+
+(defun js2-read-PI ()
+  &quot;Scan an XML processing instruction.&quot;
+  (let (c)
+    (catch 'return
+      (while (/= js2-EOF_CHAR (setq c (js2-get-char)))
+        (js2-add-to-string c)
+        (when (and (eq c ??) (eq (js2-peek-char) ?&gt;))
+          (setq c (js2-get-char))  ;; Skip &gt;
+          (js2-add-to-string c)
+          (throw 'return t)))
+      (js2-xml-discard-string)
+      nil)))
+
+(defun js2-scanner-get-line ()
+  &quot;Return the text of the current scan line.&quot;
+  (buffer-substring (point-at-bol) (point-at-eol)))
+
+;;; Highlighting
+
+(defsubst js2-set-face (beg end face &amp;optional record)
+  &quot;Fontify a region.  If RECORD is non-nil, record for later.&quot;
+  (when (plusp js2-highlight-level)
+    (setq beg (min (point-max) beg)
+          beg (max (point-min) beg)
+          end (min (point-max) end)
+          end (max (point-min) end))
+    (if record
+        (push (list beg end face) js2-mode-fontifications)
+      (put-text-property beg end 'face face))))
+
+(defsubst js2-set-kid-face (pos kid len face)
+  &quot;Set-face on a child node.
+POS is absolute buffer position of parent.
+KID is the child node.
+LEN is the length to fontify.
+FACE is the face to fontify with.&quot;
+  (js2-set-face (+ pos (js2-node-pos kid))
+                (+ pos (js2-node-pos kid) (js2-node-len kid))
+                face))
+
+(defsubst js2-fontify-kwd (start length)
+  (js2-set-face start (+ start length) 'font-lock-keyword-face))
+
+(defsubst js2-clear-face (beg end)
+  (remove-text-properties beg end '(face nil
+                                    help-echo nil
+                                    point-entered nil
+                                    c-in-sws nil)))
+
+(defsubst js2-record-text-property (beg end prop value)
+  &quot;Record a text property to set when parsing finishes.&quot;
+  (push (list beg end prop value) js2-mode-deferred-properties))
+
+(defconst js2-ecma-global-props
+  (concat &quot;^&quot;
+          (regexp-opt
+           '(&quot;Infinity&quot; &quot;NaN&quot; &quot;undefined&quot; &quot;arguments&quot;) t)
+          &quot;$&quot;)
+  &quot;Value properties of the Ecma-262 Global Object.
+Shown at or above `js2-highlight-level' 2.&quot;)
+
+;; might want to add the name &quot;arguments&quot; to this list?
+(defconst js2-ecma-object-props
+  (concat &quot;^&quot;
+          (regexp-opt
+           '(&quot;prototype&quot; &quot;__proto__&quot; &quot;__parent__&quot;) t)
+          &quot;$&quot;)
+  &quot;Value properties of the Ecma-262 Object constructor.
+Shown at or above `js2-highlight-level' 2.&quot;)
+
+(defconst js2-ecma-global-funcs
+  (concat
+   &quot;^&quot;
+   (regexp-opt
+    '(&quot;decodeURI&quot; &quot;decodeURIComponent&quot; &quot;encodeURI&quot; &quot;encodeURIComponent&quot;
+      &quot;eval&quot; &quot;isFinite&quot; &quot;isNaN&quot; &quot;parseFloat&quot; &quot;parseInt&quot;) t)
+   &quot;$&quot;)
+  &quot;Function properties of the Ecma-262 Global object.
+Shown at or above `js2-highlight-level' 2.&quot;)
+
+(defconst js2-ecma-number-props
+  (concat &quot;^&quot;
+          (regexp-opt '(&quot;MAX_VALUE&quot; &quot;MIN_VALUE&quot; &quot;NaN&quot;
+                        &quot;NEGATIVE_INFINITY&quot;
+                        &quot;POSITIVE_INFINITY&quot;) t)
+          &quot;$&quot;)
+  &quot;Properties of the Ecma-262 Number constructor.
+Shown at or above `js2-highlight-level' 2.&quot;)
+
+(defconst js2-ecma-date-props &quot;^\\(parse\\|UTC\\)$&quot;
+  &quot;Properties of the Ecma-262 Date constructor.
+Shown at or above `js2-highlight-level' 2.&quot;)
+
+(defconst js2-ecma-math-props
+  (concat &quot;^&quot;
+          (regexp-opt
+           '(&quot;E&quot; &quot;LN10&quot; &quot;LN2&quot; &quot;LOG2E&quot; &quot;LOG10E&quot; &quot;PI&quot; &quot;SQRT1_2&quot; &quot;SQRT2&quot;)
+           t)
+          &quot;$&quot;)
+  &quot;Properties of the Ecma-262 Math object.
+Shown at or above `js2-highlight-level' 2.&quot;)
+
+(defconst js2-ecma-math-funcs
+  (concat &quot;^&quot;
+          (regexp-opt
+           '(&quot;abs&quot; &quot;acos&quot; &quot;asin&quot; &quot;atan&quot; &quot;atan2&quot; &quot;ceil&quot; &quot;cos&quot; &quot;exp&quot; &quot;floor&quot;
+             &quot;log&quot; &quot;max&quot; &quot;min&quot; &quot;pow&quot; &quot;random&quot; &quot;round&quot; &quot;sin&quot; &quot;sqrt&quot; &quot;tan&quot;) t)
+          &quot;$&quot;)
+  &quot;Function properties of the Ecma-262 Math object.
+Shown at or above `js2-highlight-level' 2.&quot;)
+
+(defconst js2-ecma-function-props
+  (concat
+   &quot;^&quot;
+   (regexp-opt
+    '(;; properties of the Object prototype object
+      &quot;hasOwnProperty&quot; &quot;isPrototypeOf&quot; &quot;propertyIsEnumerable&quot;
+      &quot;toLocaleString&quot; &quot;toString&quot; &quot;valueOf&quot;
+      ;; properties of the Function prototype object
+      &quot;apply&quot; &quot;call&quot;
+      ;; properties of the Array prototype object
+      &quot;concat&quot; &quot;join&quot; &quot;pop&quot; &quot;push&quot; &quot;reverse&quot; &quot;shift&quot; &quot;slice&quot; &quot;sort&quot;
+      &quot;splice&quot; &quot;unshift&quot;
+      ;; properties of the String prototype object
+      &quot;charAt&quot; &quot;charCodeAt&quot; &quot;fromCharCode&quot; &quot;indexOf&quot; &quot;lastIndexOf&quot;
+      &quot;localeCompare&quot; &quot;match&quot; &quot;replace&quot; &quot;search&quot; &quot;split&quot; &quot;substring&quot;
+      &quot;toLocaleLowerCase&quot; &quot;toLocaleUpperCase&quot; &quot;toLowerCase&quot;
+      &quot;toUpperCase&quot;
+      ;; properties of the Number prototype object
+      &quot;toExponential&quot; &quot;toFixed&quot; &quot;toPrecision&quot;
+      ;; properties of the Date prototype object
+      &quot;getDate&quot; &quot;getDay&quot; &quot;getFullYear&quot; &quot;getHours&quot; &quot;getMilliseconds&quot;
+      &quot;getMinutes&quot; &quot;getMonth&quot; &quot;getSeconds&quot; &quot;getTime&quot;
+      &quot;getTimezoneOffset&quot; &quot;getUTCDate&quot; &quot;getUTCDay&quot; &quot;getUTCFullYear&quot;
+      &quot;getUTCHours&quot; &quot;getUTCMilliseconds&quot; &quot;getUTCMinutes&quot; &quot;getUTCMonth&quot;
+      &quot;getUTCSeconds&quot; &quot;setDate&quot; &quot;setFullYear&quot; &quot;setHours&quot;
+      &quot;setMilliseconds&quot; &quot;setMinutes&quot; &quot;setMonth&quot; &quot;setSeconds&quot; &quot;setTime&quot;
+      &quot;setUTCDate&quot; &quot;setUTCFullYear&quot; &quot;setUTCHours&quot; &quot;setUTCMilliseconds&quot;
+      &quot;setUTCMinutes&quot; &quot;setUTCMonth&quot; &quot;setUTCSeconds&quot; &quot;toDateString&quot;
+      &quot;toLocaleDateString&quot; &quot;toLocaleString&quot; &quot;toLocaleTimeString&quot;
+      &quot;toTimeString&quot; &quot;toUTCString&quot;
+      ;; properties of the RegExp prototype object
+      &quot;exec&quot; &quot;test&quot;
+      ;; SpiderMonkey/Rhino extensions, versions 1.5+
+      &quot;toSource&quot; &quot;__defineGetter__&quot; &quot;__defineSetter__&quot;
+      &quot;__lookupGetter__&quot; &quot;__lookupSetter__&quot; &quot;__noSuchMethod__&quot;
+      &quot;every&quot; &quot;filter&quot; &quot;forEach&quot; &quot;lastIndexOf&quot; &quot;map&quot; &quot;some&quot;)
+    t)
+   &quot;$&quot;)
+  &quot;Built-in functions defined by Ecma-262 and SpiderMonkey extensions.
+Shown at or above `js2-highlight-level' 3.&quot;)
+
+(defsubst js2-parse-highlight-prop-get (parent target prop call-p)
+  (let ((target-name (and target
+                          (js2-name-node-p target)
+                          (js2-name-node-name target)))
+        (prop-name (if prop (js2-name-node-name prop)))
+        (level1 (&gt;= js2-highlight-level 1))
+        (level2 (&gt;= js2-highlight-level 2))
+        (level3 (&gt;= js2-highlight-level 3))
+        pos
+        face)
+    (when level2
+      (if call-p
+          (cond
+           ((and target prop)
+            (cond
+             ((and level3 (string-match js2-ecma-function-props prop-name))
+              (setq face 'font-lock-builtin-face))
+             ((and target-name prop)
+              (cond
+               ((string= target-name &quot;Date&quot;)
+                (if (string-match js2-ecma-date-props prop-name)
+                    (setq face 'font-lock-builtin-face)))
+               ((string= target-name &quot;Math&quot;)
+                (if (string-match js2-ecma-math-funcs prop-name)
+                    (setq face 'font-lock-builtin-face)))))))
+           (prop
+            (if (string-match js2-ecma-global-funcs prop-name)
+                (setq face 'font-lock-builtin-face))))
+        (cond
+         ((and target prop)
+          (cond
+           ((string= target-name &quot;Number&quot;)
+            (if (string-match js2-ecma-number-props prop-name)
+                (setq face 'font-lock-constant-face)))
+           ((string= target-name &quot;Math&quot;)
+            (if (string-match js2-ecma-math-props prop-name)
+                (setq face 'font-lock-constant-face)))))
+         (prop
+          (if (string-match js2-ecma-object-props prop-name)
+              (setq face 'font-lock-constant-face)))))
+      (when face
+        (js2-set-face (setq pos (+ (js2-node-pos parent) ; absolute
+                                   (js2-node-pos prop))) ; relative
+                      (+ pos (js2-node-len prop))
+                      face)))))
+
+(defun js2-parse-highlight-member-expr-node (node)
+  &quot;Perform syntax highlighting of EcmaScript built-in properties.
+The variable `js2-highlight-level' governs this highighting.&quot;
+  (let (face target prop name pos end parent call-p callee)
+    (cond
+     ;; case 1:  simple name, e.g. foo
+     ((js2-name-node-p node)
+      (setq name (js2-name-node-name node))
+      ;; possible for name to be nil in rare cases - saw it when
+      ;; running js2-mode on an elisp buffer.  Might as well try to
+      ;; make it so js2-mode never barfs.
+      (when name
+        (setq face (if (string-match js2-ecma-global-props name)
+                       'font-lock-constant-face))
+        (when face
+          (setq pos (js2-node-pos node)
+                end (+ pos (js2-node-len node)))
+          (js2-set-face pos end face))))
+     ;; case 2:  property access or function call
+     ((or (js2-prop-get-node-p node)
+          ;; highlight function call if expr is a prop-get node
+          ;; or a plain name (i.e. unqualified function call)
+          (and (setq call-p (js2-call-node-p node))
+               (setq callee (js2-call-node-target node)) ; separate setq!
+               (or (js2-prop-get-node-p callee)
+                   (js2-name-node-p callee))))
+      (setq parent node
+            node (if call-p callee node))
+      (if (and call-p (js2-name-node-p callee))
+          (setq prop callee)
+        (setq target (js2-prop-get-node-left node)
+              prop (js2-prop-get-node-right node)))
+      (cond
+       ((js2-name-node-p target)
+        (if (js2-name-node-p prop)
+            ;; case 2a:  simple target, simple prop name, e.g. foo.bar
+            (js2-parse-highlight-prop-get parent target prop call-p)
+          ;; case 2b:  simple target, complex name, e.g. foo.x[y]
+          (js2-parse-highlight-prop-get parent target nil call-p)))
+       ((js2-name-node-p prop)
+        ;; case 2c:  complex target, simple name, e.g. x[y].bar
+        (js2-parse-highlight-prop-get parent target prop call-p)))))))
+
+(defun js2-parse-highlight-member-expr-fn-name (expr)
+  &quot;Highlight the `baz' in function foo.bar.baz(args) {...}.
+This is experimental Rhino syntax.  EXPR is the foo.bar.baz member expr.
+We currently only handle the case where the last component is a prop-get
+of a simple name.  Called before EXPR has a parent node.&quot;
+  (let (pos
+        (name (and (js2-prop-get-node-p expr)
+                   (js2-prop-get-node-right expr))))
+    (when (js2-name-node-p name)
+      (js2-set-face (setq pos (+ (js2-node-pos expr)  ; parent is absolute
+                                 (js2-node-pos name)))
+                    (+ pos (js2-node-len name))
+                    'font-lock-function-name-face
+                    'record))))
+
+;; source:  http://jsdoc.sourceforge.net/
+;; Note - this syntax is for Google's enhanced jsdoc parser that
+;; allows type specifications, and needs work before entering the wild.
+
+(defconst js2-jsdoc-param-tag-regexp
+  (concat &quot;^\\s-*\\*+\\s-*\\(@&quot;
+          &quot;\\(?:param\\|argument\\)&quot;
+          &quot;\\)&quot;
+          &quot;\\s-*\\({[^}]+}\\)?&quot;         ; optional type
+          &quot;\\s-*\\([a-zA-Z0-9_$]+\\)?&quot;  ; name
+          &quot;\\&gt;&quot;)
+  &quot;Matches jsdoc tags with optional type and optional param name.&quot;)
+
+(defconst js2-jsdoc-typed-tag-regexp
+  (concat &quot;^\\s-*\\*+\\s-*\\(@\\(?:&quot;
+          (regexp-opt
+           '(&quot;enum&quot;
+             &quot;extends&quot;
+             &quot;field&quot;
+             &quot;id&quot;
+             &quot;implements&quot;
+             &quot;lends&quot;
+             &quot;mods&quot;
+             &quot;requires&quot;
+             &quot;return&quot;
+             &quot;returns&quot;
+             &quot;throw&quot;
+             &quot;throws&quot;))
+          &quot;\\)\\)\\s-*\\({[^}]+}\\)?&quot;)
+  &quot;Matches jsdoc tags with optional type.&quot;)
+
+(defconst js2-jsdoc-arg-tag-regexp
+  (concat &quot;^\\s-*\\*+\\s-*\\(@\\(?:&quot;
+          (regexp-opt
+           '(&quot;alias&quot;
+             &quot;augments&quot;
+             &quot;borrows&quot;
+             &quot;bug&quot;
+             &quot;base&quot;
+             &quot;config&quot;
+             &quot;default&quot;
+             &quot;define&quot;
+             &quot;exception&quot;
+             &quot;function&quot;
+             &quot;member&quot;
+             &quot;memberOf&quot;
+             &quot;name&quot;
+             &quot;namespace&quot;
+             &quot;property&quot;
+             &quot;since&quot;
+             &quot;suppress&quot;
+             &quot;this&quot;
+             &quot;throws&quot;
+             &quot;type&quot;
+             &quot;version&quot;))
+          &quot;\\)\\)\\s-+\\([^ \t]+\\)&quot;)
+  &quot;Matches jsdoc tags with a single argument.&quot;)
+
+(defconst js2-jsdoc-empty-tag-regexp
+  (concat &quot;^\\s-*\\*+\\s-*\\(@\\(?:&quot;
+          (regexp-opt
+           '(&quot;addon&quot;
+             &quot;author&quot;
+             &quot;class&quot;
+             &quot;const&quot;
+             &quot;constant&quot;
+             &quot;constructor&quot;
+             &quot;constructs&quot;
+             &quot;deprecated&quot;
+             &quot;desc&quot;
+             &quot;description&quot;
+             &quot;event&quot;
+             &quot;example&quot;
+             &quot;exec&quot;
+             &quot;export&quot;
+             &quot;fileoverview&quot;
+             &quot;final&quot;
+             &quot;function&quot;
+             &quot;hidden&quot;
+             &quot;ignore&quot;
+             &quot;implicitCast&quot;
+             &quot;inheritDoc&quot;
+             &quot;inner&quot;
+             &quot;interface&quot;
+             &quot;license&quot;
+             &quot;noalias&quot;
+             &quot;noshadow&quot;
+             &quot;notypecheck&quot;
+             &quot;override&quot;
+             &quot;owner&quot;
+             &quot;preserve&quot;
+             &quot;preserveTry&quot;
+             &quot;private&quot;
+             &quot;protected&quot;
+             &quot;public&quot;
+             &quot;static&quot;
+             &quot;supported&quot;
+             ))
+          &quot;\\)\\)\\s-*&quot;)
+  &quot;Matches empty jsdoc tags.&quot;)
+
+(defconst js2-jsdoc-link-tag-regexp
+  &quot;{\\(@\\(?:link\\|code\\)\\)\\s-+\\([^#}\n]+\\)\\(#.+\\)?}&quot;
+  &quot;Matches a jsdoc link or code tag.&quot;)
+
+(defconst js2-jsdoc-see-tag-regexp
+  &quot;^\\s-*\\*+\\s-*\\(@see\\)\\s-+\\([^#}\n]+\\)\\(#.+\\)?&quot;
+  &quot;Matches a jsdoc @see tag.&quot;)
+
+(defconst js2-jsdoc-html-tag-regexp
+  &quot;\\(&lt;/?\\)\\([a-zA-Z]+\\)\\s-*\\(/?&gt;\\)&quot;
+  &quot;Matches a simple (no attributes) html start- or end-tag.&quot;)
+
+(defsubst js2-jsdoc-highlight-helper ()
+  (js2-set-face (match-beginning 1)
+                (match-end 1)
+                'js2-jsdoc-tag-face)
+  (if (match-beginning 2)
+      (if (save-excursion
+            (goto-char (match-beginning 2))
+            (= (char-after) ?{))
+          (js2-set-face (1+ (match-beginning 2))
+                        (1- (match-end 2))
+                        'js2-jsdoc-type-face)
+        (js2-set-face (match-beginning 2)
+                      (match-end 2)
+                      'js2-jsdoc-value-face)))
+  (if (match-beginning 3)
+      (js2-set-face (match-beginning 3)
+                    (match-end 3)
+                    'js2-jsdoc-value-face)))
+
+(defun js2-highlight-jsdoc (ast)
+  &quot;Highlight doc comment tags.&quot;
+  (let ((comments (js2-ast-root-comments ast))
+        beg end)
+    (save-excursion
+      (dolist (node comments)
+        (when (eq (js2-comment-node-format node) 'jsdoc)
+          (setq beg (js2-node-abs-pos node)
+                end (+ beg (js2-node-len node)))
+          (save-restriction
+            (narrow-to-region beg end)
+            (dolist (re (list js2-jsdoc-param-tag-regexp
+                              js2-jsdoc-typed-tag-regexp
+                              js2-jsdoc-arg-tag-regexp
+                              js2-jsdoc-link-tag-regexp
+                              js2-jsdoc-see-tag-regexp
+                              js2-jsdoc-empty-tag-regexp))
+              (goto-char beg)
+              (while (re-search-forward re nil t)
+                (js2-jsdoc-highlight-helper)))
+            ;; simple highlighting for html tags
+            (goto-char beg)
+            (while (re-search-forward js2-jsdoc-html-tag-regexp nil t)
+              (js2-set-face (match-beginning 1)
+                            (match-end 1)
+                            'js2-jsdoc-html-tag-delimiter-face)
+              (js2-set-face (match-beginning 2)
+                            (match-end 2)
+                            'js2-jsdoc-html-tag-name-face)
+              (js2-set-face (match-beginning 3)
+                            (match-end 3)
+                            'js2-jsdoc-html-tag-delimiter-face))))))))
+
+(defun js2-highlight-assign-targets (node left right)
+  &quot;Highlight function properties and external variables.&quot;
+  (let (leftpos end name)
+    ;; highlight vars and props assigned function values
+    (when (js2-function-node-p right)
+      (cond
+       ;; var foo = function() {...}
+       ((js2-name-node-p left)
+        (setq name left))
+       ;; foo.bar.baz = function() {...}
+       ((and (js2-prop-get-node-p left)
+             (js2-name-node-p (js2-prop-get-node-right left)))
+        (setq name (js2-prop-get-node-right left))))
+      (when name
+        (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))))
+
+(defun js2-highlight-undeclared-vars ()
+  &quot;After entire parse is finished, look for undeclared variable assignments.
+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.&quot;
+  (let (name)
+    (dolist (entry js2-recorded-assignments)
+      (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-face 'record)
+          (js2-record-text-property pos end 'help-echo &quot;Undeclared variable&quot;)
+          (js2-record-text-property pos end 'point-entered #'js2-echo-help))))
+    (setq js2-recorded-assignments nil)))
+
+;;; IMenu support
+
+;; We currently only support imenu, but eventually should support speedbar and
+;; 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 &lt;position&gt;), and push the
+;; list into `js2-imenu-recorder'.  The lists are merged into a trie-like tree
+;; for imenu after parsing is finished.
+
+;; A `foo.bar.baz' assignment target may be expressed in many ways in
+;; JavaScript, and the general problem is undecidable.  However, several forms
+;; are readily recognizable at parse-time; the forms we attempt to recognize
+;; include:
+
+;;  function foo()  -- function declaration
+;;  foo = function()  -- function expression assigned to variable
+;;  foo.bar.baz = function()  -- function expr assigned to nested property-get
+;;  foo = {bar: function()}  -- fun prop in object literal assigned to var
+;;  foo = {bar: {baz: function()}} -- inside nested object literal
+;;  foo.bar = {baz: function()}} -- obj lit assigned to nested prop get
+;;  a.b = {c: {d: function()}} -- nested obj lit assigned to nested prop get
+;;  foo = {get bar() {...}}  -- getter/setter in obj literal
+;;  function foo() {function bar() {...}}  -- nested function
+;;  foo['a'] = function()  -- fun expr assigned to deterministic element-get
+
+;; This list boils down to a few forms that can be combined recursively.
+;; Top-level named function declarations include both the left-hand (name)
+;; and the right-hand (function value) expressions needed to produce an imenu
+;; entry.  The other &quot;right-hand&quot; forms we need to look for are:
+;;  - functions declared as props/getters/setters in object literals
+;;  - nested named function declarations
+;; The &quot;left-hand&quot; expressions that functions can be assigned to include:
+;;  - local/global variables
+;;  - nested property-get expressions like a.b.c.d
+;;  - element gets like foo[10] or foo['bar'] where the index
+;;    expression can be trivially converted to a property name.  They
+;;    effectively then become property gets.
+
+;; All the different definition types are canonicalized into the form
+;; foo.bar.baz = position-of-function-keyword
+
+;; We need to build a trie-like structure for imenu.  As an example,
+;; consider the following JavaScript code:
+
+;; a = function() {...}  // function at position 5
+;; b = function() {...}  // function at position 25
+;; foo = function() {...} // function at position 100
+;; foo.bar = function() {...} // function at position 200
+;; foo.bar.baz = function() {...} // function at position 300
+;; foo.bar.zab = function() {...} // function at position 400
+
+;; 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))
+
+;; After parsing these entries are merged into this alist-trie:
+
+;; '((a . 1)
+;;   (b . 2)
+;;   (foo (&lt;definition&gt; . 3)
+;;        (bar (&lt;definition&gt; . 6)
+;;             (baz . 100)
+;;             (zab . 200))))
+
+;; Note the wacky need for a &lt;definition&gt; name.  The token can be anything
+;; 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)
+  &quot;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.&quot;
+  (cond
+   ((js2-name-node-p node)
+    (js2-name-node-name node))
+   ((js2-string-node-p node)
+    (js2-string-node-value node))
+   ((and (js2-number-node-p node)
+         (string-match &quot;^[0-9]+$&quot; (js2-number-node-value node)))
+    (js2-number-node-value node))
+   ((js2-this-node-p node)
+    &quot;this&quot;)))
+
+(defsubst js2-node-qname-component (node)
+  &quot;Test function:  return the name of this node, if it contributes to a qname.
+Returns nil if the node doesn't contribute.&quot;
+  (copy-sequence
+   (or (js2-prop-node-name node)
+       (if (and (js2-function-node-p node)
+                (js2-function-node-name node))
+           (js2-name-node-name (js2-function-node-name node))))))
+
+(defsubst js2-record-function-qname (fn-node qname)
+  &quot;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.&quot;
+  (unless js2-imenu-function-map
+    (setq js2-imenu-function-map (make-hash-table :test 'eq)))
+  (puthash fn-node qname js2-imenu-function-map))
+
+(defun js2-record-imenu-functions (node &amp;optional var)
+  &quot;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.&quot;
+  (when js2-parse-ide-mode
+    (let ((fun-p (js2-function-node-p node))
+          qname left fname-node pos)
+      (cond
+       ;; non-anonymous function declaration?
+       ((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))
+       ;; 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))
+         ;; foo.bar.baz = object-literal
+         ;; look for nested functions:  {a: {b: function() {...} }}
+         ((js2-object-node-p node)
+          (js2-record-object-literal node qname))))))))
+
+(defun js2-compute-nested-prop-get (node)
+  &quot;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.&quot;
+  (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 =&gt; right operand is a leaf
+     ((js2-prop-get-node-p node)  ; includes elem-get nodes
+      (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 &quot;^[0-9]+$&quot;
+                                      (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)
+  &quot;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'.&quot;
+  ;; 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)
+    (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)))))))))
+
+(defsubst js2-node-top-level-decl-p (node)
+  &quot;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.&quot;
+  (let* ((name (js2-prop-node-name node))
+         (this-scope (js2-node-get-enclosing-scope node))
+         defining-scope)
+    (cond
+     ((js2-this-node-p node)
+      nil)
+     ((null this-scope)
+      t)
+     ((setq defining-scope (js2-get-defining-scope this-scope name))
+      (js2-ast-root-p defining-scope))
+     (t t))))
+
+(defun js2-browse-postprocess-chains (chains)
+  &quot;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.&quot;
+  (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))))))
+    ;; finally replace each node in each chain with its name.
+    (dolist (chain result)
+      (setq p chain)
+      (while p
+        (if (js2-node-p (setq elem (car p)))
+            (setcar p (js2-node-qname-component elem)))
+        (setq p (cdr p))))
+    result))
+
+;; Merge name chains into a trie-like tree structure of nested lists.
+;; To simplify construction of the trie, we first build it out using the rule
+;; that the trie consists of lists of pairs.  Each pair is a 2-element array:
+;; [key, num-or-list].  The second element can be a number; if so, this key
+;; is a leaf-node with only one value.  (I.e. there is only one declaration
+;; associated with the key at this level.)  Otherwise the second element is
+;; a list of pairs, with the rule applied recursively.  This symmetry permits
+;; a simple recursive formulation.
+;;
+;; js2-mode is building the data structure for imenu.  The imenu documentation
+;; claims that it's the structure above, but in practice it wants the children
+;; at the same list level as the key for that level, which is how I've drawn
+;; the &quot;Expected final result&quot; above.  We'll postprocess the trie to remove the
+;; list wrapper around the children at each level.
+;;
+;; A completed nested imenu-alist entry looks like this:
+;;       '((&quot;foo&quot;
+;;          (&quot;&lt;definition&gt;&quot; . 7)
+;;          (&quot;bar&quot;
+;;           (&quot;a&quot; . 40)
+;;           (&quot;b&quot; . 60))))
+;;
+;; In particular, the documentation for `imenu--index-alist' says that
+;; 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)
+  &quot;Convert (a b c d) to (a ((b ((c d)))))&quot;
+  (if (null (cddr lst))  ; list length &lt;= 2
+      lst
+    (list (car lst) (list (js2-treeify (cdr lst))))))
+
+(defun js2-build-alist-trie (chains trie)
+  &quot;Merge declaration name chains into a trie-like alist structure for imenu.
+CHAINS is the qname chain list produced during parsing. TRIE is a
+list of elements built up so far.&quot;
+  (let (head tail pos branch kids)
+    (dolist (chain chains)
+      (setq head (car chain)
+            tail (cdr chain)
+            pos (if (numberp (car tail)) (car tail))
+            branch (js2-find-if (lambda (n)
+                                  (string= (car n) head))
+                                trie)
+            kids (second branch))
+      (cond
+       ;; case 1:  this key isn't in the trie yet
+       ((null branch)
+        (if trie
+            (setcdr (last trie) (list (js2-treeify chain)))
+          (setq trie (list (js2-treeify chain)))))
+       ;; case 2:  key is present with a single number entry:  replace w/ list
+       ;;  (&quot;a1&quot; 10)  +  (&quot;a1&quot; 20) =&gt; (&quot;a1&quot; ((&quot;&lt;definition&gt;&quot; 10)
+       ;;                                    (&quot;&lt;definition&gt;&quot; 20)))
+       ((numberp kids)
+        (setcar (cdr branch)
+                (list (list &quot;&lt;definition-1&gt;&quot; kids)
+                      (if pos
+                          (list &quot;&lt;definition-2&gt;&quot; pos)
+                        (js2-treeify tail)))))
+       ;; case 3:  key is there (with kids), and we're a number entry
+       (pos
+        (setcdr (last kids)
+                (list
+                 (list (format &quot;&lt;definition-%d&gt;&quot;
+                               (1+ (loop for kid in kids
+                                         count (eq ?&lt; (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))))
+    trie))
+
+(defun js2-flatten-trie (trie)
+  &quot;Convert TRIE to imenu-format.
+Recurses through nodes, and for each one whose second element is a list,
+appends the list's flattened elements to the current element.  Also
+changes the tails into conses.  For instance, this pre-flattened trie
+
+'(a ((b 20)
+     (c ((d 30)
+         (e 40)))))
+
+becomes
+
+'(a (b . 20)
+    (c (d . 30)
+       (e . 40)))
+
+Note that the root of the trie has no key, just a list of chains.
+This is also true for the value of any key with multiple children,
+e.g. key 'c' in the example above.&quot;
+  (cond
+   ((listp (car trie))
+    (mapcar #'js2-flatten-trie trie))
+   (t
+    (if (numberp (second trie))
+        (cons (car trie) (second trie))
+      ;; else pop list and append its kids
+      (apply #'append (list (car trie)) (js2-flatten-trie (cdr trie)))))))
+
+(defun js2-build-imenu-index ()
+  &quot;Turn `js2-imenu-recorder' into an imenu data structure.&quot;
+  (unless (eq js2-imenu-recorder 'empty)
+    (let* ((chains (js2-browse-postprocess-chains js2-imenu-recorder))
+           (result (js2-build-alist-trie chains nil)))
+      (js2-flatten-trie result))))
+
+(defun js2-test-print-chains (chains)
+  &quot;Print a list of qname chains.
+Each element of CHAINS is a list of the form (NODE [NODE *] pos);
+i.e. one or more nodes, and an integer position as the list tail.&quot;
+  (mapconcat (lambda (chain)
+               (concat &quot;(&quot;
+                       (mapconcat (lambda (elem)
+                                    (if (js2-node-p elem)
+                                        (or (js2-node-qname-component elem)
+                                            &quot;nil&quot;)
+                                      (number-to-string elem)))
+                                  chain
+                                  &quot; &quot;)
+                       &quot;)&quot;))
+             chains
+             &quot;\n&quot;))
+
+;;; Parser
+
+(defconst js2-version &quot;1.8.0&quot;
+  &quot;Version of JavaScript supported, plus minor js2 version.&quot;)
+
+(defmacro js2-record-face (face)
+  &quot;Record a style run of FACE for the current token.&quot;
+  `(js2-set-face js2-token-beg js2-token-end ,face 'record))
+
+(defsubst js2-node-end (n)
+  &quot;Computes the absolute end of node N.
+Use with caution!  Assumes `js2-node-pos' is -absolute-, which
+is only true until the node is added to its parent; i.e., while parsing.&quot;
+  (+ (js2-node-pos n)
+     (js2-node-len n)))
+
+(defsubst js2-record-comment ()
+  &quot;Record a comment in `js2-scanned-comments'.&quot;
+  (push (make-js2-comment-node :len (- js2-token-end js2-token-beg)
+                               :format js2-ts-comment-type)
+        js2-scanned-comments)
+  (when js2-parse-ide-mode
+    (js2-record-face (if (eq js2-ts-comment-type 'jsdoc)
+                         'font-lock-doc-face
+                       'font-lock-comment-face))
+    (when (memq js2-ts-comment-type '(html preprocessor))
+      ;; Tell cc-engine the bounds of the comment.
+      (put-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 ()
+  &quot;Returns the next token without consuming it.
+If previous token was consumed, calls scanner to get new token.
+If previous token was -not- consumed, returns it (idempotent).
+
+This function will not return a newline (js2-EOL) - instead, it
+gobbles newlines until it finds a non-newline token, and flags
+that token as appearing just after a newline.
+
+This function will also not return a js2-COMMENT.  Instead, it
+records comments found in `js2-scanned-comments'.  If the token
+returned by this function immediately follows a jsdoc comment,
+the token is flagged as such.
+
+Note that this function always returned the un-flagged token!
+The flags, if any, are saved in `js2-current-flagged-token'.&quot;
+  (if (/= js2-current-flagged-token js2-EOF) ; last token not consumed
+      js2-current-token  ; most common case - return already-peeked token
+    (let ((tt (js2-get-token))          ; call scanner
+          saw-eol
+          face)
+      ;; process comments and whitespace
+      (while (or (= tt js2-EOL)
+                 (= tt js2-COMMENT))
+        (if (= tt js2-EOL)
+            (setq saw-eol t)
+          (setq saw-eol nil)
+          (if js2-record-comments
+              (js2-record-comment)))
+        (setq tt (js2-get-token)))  ; call scanner
+      (setq js2-current-token tt
+            js2-current-flagged-token (if saw-eol
+                                          (logior tt js2-ti-after-eol)
+                                        tt))
+      ;; perform lexical fontification as soon as token is scanned
+      (when js2-parse-ide-mode
+        (cond
+         ((minusp tt)
+          (js2-record-face 'js2-error-face))
+         ((setq face (aref js2-kwd-tokens tt))
+          (js2-record-face face))
+         ((and (= tt js2-NAME)
+               (equal js2-ts-string &quot;undefined&quot;))
+          (js2-record-face 'font-lock-constant-face))))
+      tt)))  ; return unflagged token
+
+(defsubst js2-peek-flagged-token ()
+  &quot;Returns the current token along with any flags set for it.&quot;
+  (js2-peek-token)
+  js2-current-flagged-token)
+
+(defsubst js2-consume-token ()
+  (setq js2-current-flagged-token js2-EOF))
+
+(defsubst js2-next-token ()
+  (prog1
+      (js2-peek-token)
+    (js2-consume-token)))
+
+(defsubst js2-next-flagged-token ()
+  (js2-peek-token)
+  (prog1 js2-current-flagged-token
+    (js2-consume-token)))
+
+(defsubst js2-match-token (match)
+  &quot;Consume and return t if next token matches MATCH, a bytecode.
+Returns nil and consumes nothing if MATCH is not the next token.&quot;
+  (if (/= (js2-peek-token) match)
+      nil
+    (js2-consume-token)
+    t))
+
+(defsubst js2-valid-prop-name-token (tt)
+  (or (= tt js2-NAME)
+      (and js2-allow-keywords-as-property-names
+           (plusp tt)
+           (aref js2-kwd-tokens tt))))
+
+(defsubst js2-match-prop-name ()
+  &quot;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.&quot;
+  (if (js2-valid-prop-name-token (js2-peek-token))
+      (progn
+        (js2-consume-token)
+        t)
+    nil))
+
+(defsubst js2-must-match-prop-name (msg-id &amp;optional pos len)
+  (if (js2-match-prop-name)
+      t
+    (js2-report-error msg-id nil pos len)
+    nil))
+
+(defsubst js2-peek-token-or-eol ()
+  &quot;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.
+One example is the postfix ++ or -- operator, which has to be on the
+same line as its operand.&quot;
+  (let ((tt (js2-peek-token)))
+    ;; Check for last peeked token flags
+    (if (js2-flag-set-p js2-current-flagged-token js2-ti-after-eol)
+        js2-EOL
+      tt)))
+
+(defsubst js2-set-check-for-label ()
+  (assert (= (logand js2-current-flagged-token js2-clear-ti-mask) js2-NAME))
+  (js2-set-flag js2-current-flagged-token js2-ti-check-label))
+
+(defsubst js2-must-match (token msg-id &amp;optional pos len)
+  &quot;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.&quot;
+  (if (js2-match-token token)
+      t
+    (js2-report-error msg-id nil pos len)
+    nil))
+
+(defsubst js2-inside-function ()
+  (plusp js2-nesting-of-function))
+
+(defsubst 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)
+  (when (js2-inside-function)
+    ;; skip language-version 1.2 check from Rhino
+    (if (or (string= &quot;arguments&quot; name)
+            (and js2-compiler-activation-names  ; only used in codegen
+                 (gethash name js2-compiler-activation-names)))
+        (js2-set-requires-activation))))
+
+(defsubst js2-set-is-generator ()
+  (if (js2-function-node-p js2-current-script-or-fn)
+      (setf (js2-function-node-is-generator js2-current-script-or-fn) t)))
+
+(defsubst js2-must-have-xml ()
+  (unless js2-compiler-xml-available
+    (js2-report-error &quot;msg.XML.not.available&quot;)))
+
+(defsubst js2-push-scope (scope)
+  &quot;Push SCOPE, a `js2-scope', onto the lexical scope chain.&quot;
+  (assert (js2-scope-p scope))
+  (assert (null (js2-scope-parent-scope scope)))
+  (assert (not (eq js2-current-scope scope)))
+  (setf (js2-scope-parent-scope scope) js2-current-scope
+        js2-current-scope scope))
+
+(defsubst js2-pop-scope ()
+  (setq js2-current-scope
+        (js2-scope-parent-scope js2-current-scope)))
+
+(defsubst js2-enter-loop (loop-node)
+  (push loop-node js2-loop-set)
+  (push loop-node js2-loop-and-switch-set)
+  (js2-push-scope loop-node)
+  ;; Tell the current labeled statement (if any) its statement,
+  ;; and set the jump target of the first label to the loop.
+  ;; These are used in `js2-parse-continue' to verify that the
+  ;; continue target is an actual labeled loop.  (And for codegen.)
+  (when js2-labeled-stmt
+    (setf (js2-labeled-stmt-node-stmt js2-labeled-stmt) loop-node
+          (js2-label-node-loop (car (js2-labeled-stmt-node-labels
+                                     js2-labeled-stmt))) loop-node)))
+
+(defsubst js2-exit-loop ()
+  (pop js2-loop-set)
+  (pop js2-loop-and-switch-set)
+  (js2-pop-scope))
+
+(defsubst js2-enter-switch (switch-node)
+  (push switch-node js2-loop-and-switch-set))
+
+(defsubst js2-exit-switch ()
+  (pop js2-loop-and-switch-set))
+
+(defun js2-parse (&amp;optional buf cb)
+  &quot;Tells the js2 parser to parse a region of JavaScript.
+
+BUF is a buffer or buffer name containing the code to parse.
+Call `narrow-to-region' first to parse only part of the buffer.
+
+The returned AST root node is given some additional properties:
+  `node-count' - total number of nodes in the AST
+  `buffer' - BUF.  The buffer it refers to may change or be killed,
+             so the value is not necessarily reliable.
+
+An optional callback CB can be specified to report parsing
+progress.  If `(functionp CB)' returns t, it will be called with
+the current line number once before parsing begins, then again
+each time the lexer reaches a new line number.
+
+CB can also be a list of the form `(symbol cb ...)' to specify
+multiple callbacks with different criteria.  Each symbol is a
+criterion keyword, and the following element is the callback to
+call
+
+  :line  - called whenever the line number changes
+  :token - called for each new token consumed
+
+The list of criteria could be extended to include entering or
+leaving a statement, an expression, or a function definition.&quot;
+  (if (and cb (not (functionp cb)))
+      (error &quot;criteria callbacks not yet implemented&quot;))
+  (let ((inhibit-point-motion-hooks t)
+        (js2-compiler-xml-available (&gt;= js2-language-version 160))
+        ;; This is a recursive-descent parser, so give it a big stack.
+        (max-lisp-eval-depth (max max-lisp-eval-depth 3000))
+        (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
+    (save-excursion
+      (set-buffer buf)
+      (setq js2-scanned-comments nil
+            js2-parsed-errors nil
+            js2-parsed-warnings nil
+            js2-imenu-recorder nil
+            js2-imenu-function-map nil
+            js2-label-set nil)
+      (js2-init-scanner)
+      (setq ast (js2-with-unmodifying-text-property-changes
+                  (js2-do-parse)))
+      (unless js2-ts-hit-eof
+        (js2-report-error &quot;msg.got.syntax.errors&quot; (length js2-parsed-errors)))
+      (setf (js2-ast-root-errors ast) js2-parsed-errors
+            (js2-ast-root-warnings ast) js2-parsed-warnings)
+      ;; if we didn't find any declarations, put a dummy in this list so we
+      ;; don't end up re-parsing the buffer in `js2-mode-create-imenu-index'
+      (unless js2-imenu-recorder
+        (setq js2-imenu-recorder 'empty))
+      (run-hooks 'js2-parse-finished-hook)
+      ast)))
+
+;; Corresponds to Rhino's Parser.parse() method.
+(defun js2-do-parse ()
+  &quot;Parse current buffer starting from current point.
+Scanner should be initialized.&quot;
+  (let ((pos js2-ts-cursor)
+        (end js2-ts-cursor)  ; in case file is empty
+        root n tt)
+    ;; initialize buffer-local parsing vars
+    (setf root (make-js2-ast-root :buffer (buffer-name) :pos pos)
+          js2-current-script-or-fn root
+          js2-current-scope root
+          js2-current-flagged-token js2-EOF
+          js2-nesting-of-function 0
+          js2-labeled-stmt nil
+          js2-recorded-assignments 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))
+        ;; not a function - parse a statement
+        (setq n (js2-parse-statement)))
+      ;; add function or statement to script
+      (setq end (js2-node-end n))
+      (js2-block-node-push root n))
+    ;; add comments to root in lexical order
+    (when js2-scanned-comments
+      ;; if we find a comment beyond end of normal kids, use its end
+      (setq end (max end (js2-node-end (first js2-scanned-comments))))
+      (dolist (comment js2-scanned-comments)
+        (push comment (js2-ast-root-comments root))
+        (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)
+    root))
+
+(defun js2-function-parser ()
+  (js2-consume-token)
+  (js2-parse-function 'FUNCTION_EXPRESSION_STATEMENT))
+
+(defun js2-parse-function-closure-body (fn-node)
+  &quot;Parse a JavaScript 1.8 function closure body.&quot;
+  (let ((js2-nesting-of-function (1+ js2-nesting-of-function)))
+    (if js2-ts-hit-eof
+        (js2-report-error &quot;msg.no.brace.body&quot; nil
+                          (js2-node-pos fn-node)
+                          (- js2-ts-cursor (js2-node-pos fn-node)))
+      (js2-node-add-children fn-node
+                             (setf (js2-function-node-body fn-node)
+                                   (js2-parse-expr))))))
+
+(defun js2-parse-function-body (fn-node)
+  (js2-must-match js2-LC &quot;msg.no.brace.body&quot;
+                  (js2-node-pos fn-node)
+                  (- js2-ts-cursor (js2-node-pos fn-node)))
+  (let ((pos js2-token-beg)         ; LC position
+        (pn (make-js2-block-node))  ; starts at LC position
+        tt
+        end)
+    (incf js2-nesting-of-function)
+    (unwind-protect
+        (while (not (or (= (setq tt (js2-peek-token)) js2-ERROR)
+                        (= tt js2-EOF)
+                        (= tt js2-RC)))
+          (js2-block-node-push pn (if (/= tt js2-FUNCTION)
+                                      (js2-parse-statement)
+                                    (js2-consume-token)
+                                    (js2-parse-function 'FUNCTION_STATEMENT))))
+      (decf js2-nesting-of-function))
+    (setq end js2-token-end)  ; assume no curly and leave at current token
+    (if (js2-must-match js2-RC &quot;msg.no.brace.after.body&quot; pos)
+        (setq end js2-token-end))
+    (setf (js2-node-pos pn) pos
+          (js2-node-len pn) (- end pos))
+    (setf (js2-function-node-body fn-node) pn)
+    (js2-node-add-children fn-node pn)
+    pn))
+
+(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)
+      (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
+             (t
+              (js2-must-match js2-NAME &quot;msg.no.parm&quot;)
+              (js2-record-face 'js2-function-param-face)
+              (setq param (js2-create-name-node))
+              (js2-define-symbol js2-LP js2-ts-string param)
+              (push param params)))
+            while
+            (js2-match-token js2-COMMA))
+      (if (js2-must-match js2-RP &quot;msg.no.paren.after.parms&quot;)
+          (setf (js2-function-node-rp fn-node) (- js2-token-beg pos)))
+      (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)
+  &quot;Possibly show inconsistent-return warning.
+Last token scanned is the close-curly for the function body.&quot;
+  (when (and js2-mode-show-strict-warnings
+             js2-strict-inconsistent-return-warning
+             (not (js2-has-consistent-return-usage
+                   (js2-function-node-body fn-node))))
+    ;; Have it extend from close-curly to bol or beginning of block.
+    (let ((pos (save-excursion
+                 (goto-char js2-token-end)
+                 (max (js2-node-abs-pos (js2-function-node-body fn-node))
+                      (point-at-bol))))
+          (end js2-token-end))
+      (if (plusp (js2-name-node-length name))
+          (js2-add-strict-warning &quot;msg.no.return.value&quot;
+                                  (js2-name-node-name name) pos end)
+        (js2-add-strict-warning &quot;msg.anon.no.return.value&quot; nil pos end)))))
+
+(defun js2-parse-function (function-type)
+  &quot;Function parser.  FUNCTION-TYPE is a symbol.&quot;
+  (let ((pos js2-token-beg)  ; start of 'function' keyword
+        name
+        name-beg
+        name-end
+        fn-node
+        lp
+        (synthetic-type function-type)
+        member-expr-node)
+    ;; parse function name, expression, or non-name (anonymous)
+    (cond
+     ;; function foo(...)
+     ((js2-match-token js2-NAME)
+      (setq name (js2-create-name-node t)
+            name-beg js2-token-beg
+            name-end js2-token-end)
+      (unless (js2-match-token js2-LP)
+        (when js2-allow-member-expr-as-function-name
+          ;; function foo.bar(...)
+          (setq member-expr-node name
+                name nil
+                member-expr-node (js2-parse-member-expr-tail
+                                  nil member-expr-node)))
+        (js2-must-match js2-LP &quot;msg.no.paren.parms&quot;)))
+     ((js2-match-token js2-LP)
+      nil)  ; anonymous function:  leave name as null
+     (t
+      ;; function random-member-expr(...)
+      (when js2-allow-member-expr-as-function-name
+        ;; Note that memberExpr can not start with '(' like
+        ;; in function (1+2).toString(), because 'function (' already
+        ;; processed as anonymous function
+        (setq member-expr-node (js2-parse-member-expr)))
+      (js2-must-match js2-LP &quot;msg.no.paren.parms&quot;)))
+    (if (= js2-current-token js2-LP)  ; eventually matched LP?
+        (setq lp js2-token-beg))
+    (if member-expr-node
+        (progn
+          (setq synthetic-type 'FUNCTION_EXPRESSION)
+          (js2-parse-highlight-member-expr-fn-name member-expr-node))
+      (if name
+          (js2-set-face name-beg name-end
+                        'font-lock-function-name-face 'record)))
+    (if (and (not (eq synthetic-type 'FUNCTION_EXPRESSION))
+             (plusp (js2-name-node-length name)))
+        ;; Function statements define a symbol in the enclosing scope
+        (js2-define-symbol js2-FUNCTION (js2-name-node-name name) fn-node))
+    (setf fn-node (make-js2-function-node :pos pos
+                                          :name name
+                                          :form function-type
+                                          :lp (if lp (- lp pos))))
+    (if (or (js2-inside-function) (plusp js2-nesting-of-with))
+        ;; 1. Nested functions are not affected by the dynamic scope flag
+        ;;    as dynamic scope is already a parent of their scope.
+        ;; 2. Functions defined under the with statement also immune to
+        ;;    this setup, in which case dynamic scope is ignored in favor
+        ;;    of the with object.
+        (setf (js2-function-node-ignore-dynamic fn-node) t))
+    ;; dynamically bind all the per-function variables
+    (let ((js2-current-script-or-fn fn-node)
+          (js2-current-scope fn-node)
+          (js2-nesting-of-with 0)
+          (js2-end-flags 0)
+          js2-label-set
+          js2-loop-set
+          js2-loop-and-switch-set)
+      (js2-parse-function-params fn-node pos)
+      (if (and (&gt;= js2-language-version 180)
+               (/= (js2-peek-token) js2-LC))
+          (js2-parse-function-closure-body fn-node)
+        (js2-parse-function-body fn-node))
+      (if name
+          (js2-node-add-children fn-node name))
+      (js2-check-inconsistent-return-warning fn-node name)
+      ;; Function expressions define a name only in the body of the
+      ;; function, and only if not hidden by a parameter name
+      (if (and name
+               (eq synthetic-type 'FUNCTION_EXPRESSION)
+               (null (js2-scope-get-symbol js2-current-scope
+                                           (js2-name-node-name name))))
+          (js2-define-symbol js2-FUNCTION
+                             (js2-name-node-name name)
+                             fn-node))
+      (if (and name
+               (eq function-type 'FUNCTION_EXPRESSION_STATEMENT))
+          (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
+    ;; Rhino doesn't do this, but we need it for finding undeclared vars.
+    ;; We wait until after parsing the function to set its parent scope,
+    ;; since `js2-define-symbol' needs the defining-scope check to stop
+    ;; at the function boundary when checking for redeclarations.
+    (setf (js2-scope-parent-scope fn-node) js2-current-scope)
+    fn-node))
+
+(defun js2-parse-statements (&amp;optional parent)
+  &quot;Parse a statement list.  Last token consumed must be js2-LC.
+
+PARENT can be a `js2-block-node', in which case the statements are
+appended to PARENT.  Otherwise a new `js2-block-node' is created
+and returned.
+
+This function does not match the closing js2-RC: the caller
+matches the RC so it can provide a suitable error message if not
+matched.  This means it's up to the caller to set the length of
+the node to include the closing RC.  The node start pos is set to
+the absolute buffer start position, and the caller should fix it
+up to be relative to the parent node.  All children of this block
+node are given relative start positions and correct lengths.&quot;
+  (let ((pn (or parent (make-js2-block-node)))
+        tt)
+    (setf (js2-node-pos pn) js2-token-beg)
+    (while (and (&gt; (setq tt (js2-peek-token)) js2-EOF)
+                (/= tt js2-RC))
+      (js2-block-node-push pn (js2-parse-statement)))
+    pn))
+
+(defun js2-parse-statement ()
+  (let (tt pn beg end)
+    ;; coarse-grained user-interrupt check - needs work
+    (and js2-parse-interruptable-p
+         (zerop (% (incf js2-parse-stmt-count)
+                   js2-statements-per-pause))
+         (input-pending-p)
+         (throw 'interrupted t))
+    (setq pn (js2-statement-helper))
+    ;; no-side-effects warning check
+    (unless (js2-node-has-side-effects pn)
+      (setq end (js2-node-end pn))
+      (save-excursion
+        (goto-char end)
+        (setq beg (max (js2-node-pos pn) (point-at-bol))))
+      (js2-add-strict-warning &quot;msg.no.side.effects&quot; nil beg end))
+    pn))
+
+;; These correspond to the switch cases in Parser.statementHelper
+(defconst js2-parsers
+  (let ((parsers (make-vector js2-num-tokens
+                                #'js2-parse-expr-stmt)))
+    (aset parsers js2-BREAK     #'js2-parse-break)
+    (aset parsers js2-CONST     #'js2-parse-const-var)
+    (aset parsers js2-CONTINUE  #'js2-parse-continue)
+    (aset parsers js2-DEBUGGER  #'js2-parse-debugger)
+    (aset parsers js2-DEFAULT   #'js2-parse-default-xml-namespace)
+    (aset parsers js2-DO        #'js2-parse-do)
+    (aset parsers js2-FOR       #'js2-parse-for)
+    (aset parsers js2-FUNCTION  #'js2-function-parser)
+    (aset parsers js2-IF        #'js2-parse-if)
+    (aset parsers js2-LC        #'js2-parse-block)
+    (aset parsers js2-LET       #'js2-parse-let-stmt)
+    (aset parsers js2-NAME      #'js2-parse-name-or-label)
+    (aset parsers js2-RETURN    #'js2-parse-ret-yield)
+    (aset parsers js2-SEMI      #'js2-parse-semi)
+    (aset parsers js2-SWITCH    #'js2-parse-switch)
+    (aset parsers js2-THROW     #'js2-parse-throw)
+    (aset parsers js2-TRY       #'js2-parse-try)
+    (aset parsers js2-VAR       #'js2-parse-const-var)
+    (aset parsers js2-WHILE     #'js2-parse-while)
+    (aset parsers js2-WITH      #'js2-parse-with)
+    (aset parsers js2-YIELD     #'js2-parse-ret-yield)
+    parsers)
+  &quot;A vector mapping token types to parser functions.&quot;)
+
+(defsubst js2-parse-warn-missing-semi (beg end)
+  (and js2-mode-show-strict-warnings
+       js2-strict-missing-semi-warning
+       (js2-add-strict-warning
+        &quot;msg.missing.semi&quot; nil
+        ;; back up to beginning of statement or line
+        (max beg (save-excursion
+                   (goto-char end)
+                   (point-at-bol)))
+        end)))
+
+(defconst js2-no-semi-insertion
+  (list js2-IF
+        js2-SWITCH
+        js2-WHILE
+        js2-DO
+        js2-FOR
+        js2-TRY
+        js2-WITH
+        js2-LC
+        js2-ERROR
+        js2-SEMI
+        js2-FUNCTION)
+  &quot;List of tokens that don't do automatic semicolon insertion.&quot;)
+
+(defconst js2-autoinsert-semi-and-warn
+  (list js2-ERROR js2-EOF js2-RC))
+
+(defun js2-statement-helper ()
+  (let* ((tt (js2-peek-token))
+         (first-tt tt)
+         (beg js2-token-beg)
+         (parser (if (= tt js2-ERROR)
+                     #'js2-parse-semi
+                   (aref js2-parsers tt)))
+         pn
+         tt-flagged)
+    ;; If the statement is set, then it's been told its label by now.
+    (and js2-labeled-stmt
+         (js2-labeled-stmt-node-stmt js2-labeled-stmt)
+         (setq js2-labeled-stmt nil))
+    (setq pn (funcall parser))
+    ;; Don't do auto semi insertion for certain statement types.
+    (unless (or (memq first-tt js2-no-semi-insertion)
+                (js2-labeled-stmt-node-p pn))
+      (js2-auto-insert-semicolon pn))
+    pn))
+
+(defun js2-auto-insert-semicolon (pn)
+  (let* ((tt-flagged (js2-peek-flagged-token))
+         (tt (logand tt-flagged js2-clear-ti-mask))
+         (pos (js2-node-pos pn)))
+      (cond
+       ((= tt js2-SEMI)
+        ;; Consume ';' as a part of expression
+        (js2-consume-token)
+        ;; extend the node bounds to include the semicolon.
+        (setf (js2-node-len pn) (- js2-token-end pos)))
+       ((memq tt js2-autoinsert-semi-and-warn)
+        ;; Autoinsert ;
+        (js2-parse-warn-missing-semi pos (js2-node-end pn)))
+       (t
+        (if (js2-flag-not-set-p tt-flagged js2-ti-after-eol)
+            ;; Report error if no EOL or autoinsert ';' otherwise
+            (js2-report-error &quot;msg.no.semi.stmt&quot;)
+          (js2-parse-warn-missing-semi pos (js2-node-end pn)))))))
+
+(defun js2-parse-condition ()
+  &quot;Parse a parenthesized boolean expression, e.g. in an if- or while-stmt.
+The parens are discarded and the expression node is returned.
+The `pos' field of the return value is set to an absolute position
+that must be fixed up by the caller.
+Return value is a list (EXPR LP RP), with absolute paren positions.&quot;
+  (let (pn lp rp)
+    (if (js2-must-match js2-LP &quot;msg.no.paren.cond&quot;)
+        (setq lp js2-token-beg))
+    (setq pn (js2-parse-expr))
+    (if (js2-must-match js2-RP &quot;msg.no.paren.after.cond&quot;)
+        (setq rp js2-token-beg))
+    ;; Report strict warning on code like &quot;if (a = 7) ...&quot;
+    (if (and js2-strict-cond-assign-warning
+             (js2-assign-node-p pn))
+        (js2-add-strict-warning &quot;msg.equal.as.assign&quot; nil
+                                (js2-node-pos pn)
+                                (+ (js2-node-pos pn)
+                                   (js2-node-len pn))))
+    (list pn lp rp)))
+
+(defun js2-parse-if ()
+  &quot;Parser for if-statement.  Last matched token must be js2-IF.&quot;
+  (let ((pos js2-token-beg)
+        cond
+        if-true
+        if-false
+        else-pos
+        end
+        pn)
+    (js2-consume-token)
+    (setq cond (js2-parse-condition)
+          if-true (js2-parse-statement)
+          if-false (if (js2-match-token js2-ELSE)
+                       (progn
+                         (setq else-pos (- js2-token-beg pos))
+                         (js2-parse-statement)))
+          end (js2-node-end (or if-false if-true))
+          pn (make-js2-if-node :pos pos
+                               :len (- end pos)
+                               :condition (car cond)
+                               :then-part if-true
+                               :else-part if-false
+                               :else-pos else-pos
+                               :lp (js2-relpos (second cond) pos)
+                               :rp (js2-relpos (third cond) pos)))
+    (js2-node-add-children pn (car cond) if-true if-false)
+    pn))
+
+(defun js2-parse-switch ()
+  &quot;Parser for if-statement.  Last matched token must be js2-SWITCH.&quot;
+  (let ((pos js2-token-beg)
+        tt
+        pn
+        discriminant
+        has-default
+        case-expr
+        case-node
+        case-pos
+        cases
+        stmt
+        lp
+        rp)
+    (js2-consume-token)
+    (if (js2-must-match js2-LP &quot;msg.no.paren.switch&quot;)
+        (setq lp js2-token-beg))
+    (setq discriminant (js2-parse-expr)
+          pn (make-js2-switch-node :discriminant discriminant
+                                   :pos pos
+                                   :lp (js2-relpos lp pos)))
+    (js2-node-add-children pn discriminant)
+    (js2-enter-switch pn)
+    (unwind-protect
+        (progn
+          (if (js2-must-match js2-RP &quot;msg.no.paren.after.switch&quot;)
+              (setf (js2-switch-node-rp pn) (- js2-token-beg pos)))
+          (js2-must-match js2-LC &quot;msg.no.brace.switch&quot;)
+          (catch 'break
+            (while t
+              (setq tt (js2-next-token)
+                    case-pos js2-token-beg)
+              (cond
+               ((= tt js2-RC)
+                (setf (js2-node-len pn) (- js2-token-end pos))
+                (throw 'break nil))  ; done
+               ((= tt js2-CASE)
+                (setq case-expr (js2-parse-expr))
+                (js2-must-match js2-COLON &quot;msg.no.colon.case&quot;))
+               ((= tt js2-DEFAULT)
+                (if has-default
+                    (js2-report-error &quot;msg.double.switch.default&quot;))
+                (setq has-default t
+                      case-expr nil)
+                (js2-must-match js2-COLON &quot;msg.no.colon.case&quot;))
+               (t
+                (js2-report-error &quot;msg.bad.switch&quot;)
+                (throw 'break nil)))
+              (setq case-node (make-js2-case-node :pos case-pos
+                                                  :len (- js2-token-end case-pos)
+                                                  :expr case-expr))
+              (js2-node-add-children case-node case-expr)
+              (while (and (/= (setq tt (js2-peek-token)) js2-RC)
+                          (/= tt js2-CASE)
+                          (/= tt js2-DEFAULT)
+                          (/= tt js2-EOF))
+                (setf stmt (js2-parse-statement)
+                      (js2-node-len case-node) (- (js2-node-end stmt) case-pos))
+                (js2-block-node-push case-node stmt))
+              (push case-node cases)))
+          ;; add cases last, as pushing reverses the order to be correct
+          (dolist (kid cases)
+            (js2-node-add-children pn kid)
+            (push kid (js2-switch-node-cases pn)))
+          pn)  ; return value
+      (js2-exit-switch))))
+
+(defun js2-parse-while ()
+  &quot;Parser for while-statement.  Last matched token must be js2-WHILE.&quot;
+  (let ((pos js2-token-beg)
+        (pn (make-js2-while-node))
+        cond
+        body)
+    (js2-consume-token)
+    (js2-enter-loop pn)
+    (unwind-protect
+        (progn
+          (setf cond (js2-parse-condition)
+                (js2-while-node-condition pn) (car cond)
+                body (js2-parse-statement)
+                (js2-while-node-body pn) body
+                (js2-node-len pn) (- (js2-node-end body) pos)
+                (js2-while-node-lp pn) (js2-relpos (second cond) pos)
+                (js2-while-node-rp pn) (js2-relpos (third cond) pos))
+          (js2-node-add-children pn body (car cond)))
+      (js2-exit-loop))
+    pn))
+
+(defun js2-parse-do ()
+  &quot;Parser for do-statement.  Last matched token must be js2-DO.&quot;
+  (let ((pos js2-token-beg)
+        (pn (make-js2-do-node))
+        cond
+        body
+        end)
+    (js2-consume-token)
+    (js2-enter-loop pn)
+    (unwind-protect
+        (progn
+          (setq body (js2-parse-statement))
+          (js2-must-match js2-WHILE &quot;msg.no.while.do&quot;)
+          (setf (js2-do-node-while-pos pn) (- js2-token-beg pos)
+                cond (js2-parse-condition)
+                (js2-do-node-condition pn) (car cond)
+                (js2-do-node-body pn) body
+                end js2-ts-cursor
+                (js2-do-node-lp pn) (js2-relpos (second cond) pos)
+                (js2-do-node-rp pn) (js2-relpos (third cond) pos))
+          (js2-node-add-children pn (car cond) body))
+      (js2-exit-loop))
+    ;; Always auto-insert semicolon to follow SpiderMonkey:
+    ;; It is required by ECMAScript but is ignored by the rest of
+    ;; world; see bug 238945
+    (if (js2-match-token js2-SEMI)
+        (setq end js2-ts-cursor))
+    (setf (js2-node-len pn) (- end pos))
+    pn))
+
+(defun js2-parse-for ()
+  &quot;Parser for for-statement.  Last matched token must be js2-FOR.
+Parses for, for-in, and for each-in statements.&quot;
+  (let ((for-pos js2-token-beg)
+        pn
+        is-for-each
+        is-for-in
+        in-pos
+        each-pos
+        tmp-pos
+        init  ; Node init is also foo in 'foo in object'
+        cond  ; Node cond is also object in 'foo in object'
+        incr  ; 3rd section of for-loop initializer
+        body
+        tt
+        lp
+        rp)
+    (js2-consume-token)
+    ;; See if this is a for each () instead of just a for ()
+    (when (js2-match-token js2-NAME)
+      (if (string= &quot;each&quot; js2-ts-string)
+          (progn
+            (setq is-for-each t
+                  each-pos (- js2-token-beg for-pos)) ; relative
+            (js2-record-face 'font-lock-keyword-face))
+        (js2-report-error &quot;msg.no.paren.for&quot;)))
+    (if (js2-must-match js2-LP &quot;msg.no.paren.for&quot;)
+        (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 &quot;msg.no.semi.for&quot;)
+      (setq cond (if (= (js2-peek-token) js2-SEMI)
+                     (make-js2-empty-expr-node) ; no loop condition
+                   (js2-parse-expr)))
+      (js2-must-match js2-SEMI &quot;msg.no.semi.for.cond&quot;)
+      (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 &quot;msg.no.paren.for.ctrl&quot;)
+        (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)
+                 (&gt; (length (js2-var-decl-node-kids init)) 1))
+            (js2-report-error &quot;msg.mult.index&quot;)))
+      (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)))
+    (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))
+    pn))
+
+(defun js2-parse-try ()
+  &quot;Parser for try-statement.  Last matched token must be js2-TRY.&quot;
+  (let ((try-pos js2-token-beg)
+        try-end
+        try-block
+        catch-blocks
+        finally-block
+        saw-default-catch
+        peek
+        var-name
+        catch-cond
+        catch-node
+        guard-kwd
+        catch-pos
+        finally-pos
+        pn
+        block
+        lp
+        rp)
+    (js2-consume-token)
+    (if (/= (js2-peek-token) js2-LC)
+        (js2-report-error &quot;msg.no.brace.try&quot;))
+    (setq try-block (js2-parse-statement)
+          try-end (js2-node-end try-block)
+          peek (js2-peek-token))
+    (cond
+     ((= peek js2-CATCH)
+      (while (js2-match-token js2-CATCH)
+        (setq catch-pos js2-token-beg
+              guard-kwd nil
+              catch-cond nil
+              lp nil
+              rp nil)
+        (if saw-default-catch
+            (js2-report-error &quot;msg.catch.unreachable&quot;))
+        (if (js2-must-match js2-LP &quot;msg.no.paren.catch&quot;)
+            (setq lp (- js2-token-beg catch-pos)))
+        (js2-must-match js2-NAME &quot;msg.bad.catchcond&quot;)
+        (setq var-name (js2-create-name-node))
+        (if (js2-match-token js2-IF)
+            (setq guard-kwd (- js2-token-beg catch-pos)
+                  catch-cond (js2-parse-expr))
+          (setq saw-default-catch t))
+        (if (js2-must-match js2-RP &quot;msg.bad.catchcond&quot;)
+            (setq rp (- js2-token-beg catch-pos)))
+        (js2-must-match js2-LC &quot;msg.no.brace.catchblock&quot;)
+        (setq block (js2-parse-statements)
+              try-end (js2-node-end block)
+              catch-node (make-js2-catch-node :pos catch-pos
+                                              :var-name var-name
+                                              :guard-expr catch-cond
+                                              :guard-kwd guard-kwd
+                                              :block block
+                                              :lp lp
+                                              :rp rp))
+        (if (js2-must-match js2-RC &quot;msg.no.brace.after.body&quot;)
+            (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)
+        (push catch-node catch-blocks)))
+     ((/= peek js2-FINALLY)
+      (js2-must-match js2-FINALLY &quot;msg.try.no.catchfinally&quot;
+                      (js2-node-pos try-block)
+                      (- (setq try-end (js2-node-end try-block))
+                         (js2-node-pos try-block)))))
+    (when (js2-match-token js2-FINALLY)
+      (setq finally-pos js2-token-beg
+            block (js2-parse-statement)
+            try-end (js2-node-end block)
+            finally-block (make-js2-finally-node :pos finally-pos
+                                                 :len (- try-end finally-pos)
+                                                 :body block))
+      (js2-node-add-children finally-block block))
+    (setq pn (make-js2-try-node :pos try-pos
+                                :len (- try-end try-pos)
+                                :try-block try-block
+                                :finally-block finally-block))
+    (js2-node-add-children pn try-block finally-block)
+    ;; push them onto the try-node, which reverses and corrects their order
+    (dolist (cb catch-blocks)
+      (js2-node-add-children pn cb)
+      (push cb (js2-try-node-catch-clauses pn)))
+    pn))
+
+(defun js2-parse-throw ()
+  &quot;Parser for throw-statement.  Last matched token must be js2-THROW.&quot;
+  (let ((pos js2-token-beg)
+        expr
+        pn)
+    (js2-consume-token)
+    (if (= (js2-peek-token-or-eol) js2-EOL)
+        ;; ECMAScript does not allow new lines before throw expression,
+        ;; see bug 256617
+        (js2-report-error &quot;msg.bad.throw.eol&quot;))
+    (setq expr (js2-parse-expr)
+          pn (make-js2-throw-node :pos pos
+                                  :len (- (js2-node-end expr) pos)
+                                  :expr expr))
+    (js2-node-add-children pn expr)
+    pn))
+
+(defsubst js2-match-jump-label-name (label-name)
+  &quot;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.&quot;
+  (let ((bundle (cdr (assoc label-name js2-label-set))))
+    (if (null bundle)
+        (js2-report-error &quot;msg.undef.label&quot;))
+    bundle))
+
+(defun js2-parse-break ()
+  &quot;Parser for break-statement.  Last matched token must be js2-BREAK.&quot;
+  (let ((pos js2-token-beg)
+        (end js2-token-end)
+        break-target ; statement to break from
+        break-label  ; in &quot;break foo&quot;, name-node representing the foo
+        labels       ; matching labeled statement to break to
+        pn)
+    (js2-consume-token)  ; `break'
+    (when (eq (js2-peek-token-or-eol) js2-NAME)
+      (js2-consume-token)
+      (setq break-label (js2-create-name-node)
+            end (js2-node-end break-label)
+            ;; matchJumpLabelName only matches if there is one
+            labels (js2-match-jump-label-name js2-ts-string)
+            break-target (if labels (car (js2-labeled-stmt-node-labels labels)))))
+    (unless (or break-target break-label)
+      ;; no break target specified - try for innermost enclosing loop/switch
+      (if (null js2-loop-and-switch-set)
+          (unless break-label
+            (js2-report-error &quot;msg.bad.break&quot; nil pos (length &quot;break&quot;)))
+        (setq break-target (car js2-loop-and-switch-set))))
+    (setq pn (make-js2-break-node :pos pos
+                                  :len (- end pos)
+                                  :label break-label
+                                  :target break-target))
+    (js2-node-add-children pn break-label)  ; but not break-target
+    pn))
+
+(defun js2-parse-continue ()
+  &quot;Parser for continue-statement.  Last matched token must be js2-CONTINUE.&quot;
+  (let ((pos js2-token-beg)
+        (end js2-token-end)
+        label   ; optional user-specified label, a `js2-name-node'
+        labels  ; current matching labeled stmt, if any
+        target  ; the `js2-loop-node' target of this continue stmt
+        pn)
+    (js2-consume-token)  ; `continue'
+    (when (= (js2-peek-token-or-eol) js2-NAME)
+      (js2-consume-token)
+      (setq label (js2-create-name-node)
+            end (js2-node-end label)
+            ;; matchJumpLabelName only matches if there is one
+            labels (js2-match-jump-label-name js2-ts-string)))
+    (cond
+     ((null labels)  ; no current label to go to
+      (if (null js2-loop-set)  ; no loop to continue to
+          (js2-report-error &quot;msg.continue.outside&quot; nil pos
+                            (length &quot;continue&quot;))
+        (setq target (car js2-loop-set))))  ; innermost enclosing loop
+     (t
+      (if (js2-loop-node-p (js2-labeled-stmt-node-stmt labels))
+          (setq target (js2-labeled-stmt-node-stmt labels))
+        (js2-report-error &quot;msg.continue.nonloop&quot; nil pos (- end pos)))))
+    (setq pn (make-js2-continue-node :pos pos
+                                     :len (- end pos)
+                                     :label label
+                                     :target target))
+    (js2-node-add-children pn label)  ; but not target - it's not our child
+    pn))
+
+(defun js2-parse-with ()
+  &quot;Parser for with-statement.  Last matched token must be js2-WITH.&quot;
+  (js2-consume-token)
+  (let ((pos js2-token-beg)
+        obj body pn lp rp)
+    (if (js2-must-match js2-LP &quot;msg.no.paren.with&quot;)
+        (setq lp js2-token-beg))
+    (setq obj (js2-parse-expr))
+    (if (js2-must-match js2-RP &quot;msg.no.paren.after.with&quot;)
+        (setq rp js2-token-beg))
+    (let ((js2-nesting-of-with (1+ js2-nesting-of-with)))
+        (setq body (js2-parse-statement)))
+    (setq pn (make-js2-with-node :pos pos
+                                 :len (- (js2-node-end body) pos)
+                                 :object obj
+                                 :body body
+                                 :lp (js2-relpos lp pos)
+                                 :rp (js2-relpos rp pos)))
+    (js2-node-add-children pn obj body)
+    pn))
+
+(defun js2-parse-const-var ()
+  &quot;Parser for var- or const-statement.
+Last matched token must be js2-CONST or js2-VAR.&quot;
+  (let ((tt (js2-peek-token))
+        (pos js2-token-beg)
+        expr
+        pn)
+    (js2-consume-token)
+    (setq expr (js2-parse-variables tt js2-token-beg)
+          pn (make-js2-expr-stmt-node :pos pos
+                                      :len (- (js2-node-end expr) pos)
+                                      :expr expr))
+    (js2-node-add-children pn expr)
+    pn))
+
+(defsubst js2-wrap-with-expr-stmt (pos expr &amp;optional add-child)
+  (let ((pn (make-js2-expr-stmt-node :pos pos
+                                     :len (js2-node-len expr)
+                                     :type (if (js2-inside-function)
+                                               js2-EXPR_VOID
+                                             js2-EXPR_RESULT)
+                                     :expr expr)))
+    (if add-child
+        (js2-node-add-children pn expr))
+    pn))
+
+(defun js2-parse-let-stmt ()
+  &quot;Parser for let-statement.  Last matched token must be js2-LET.&quot;
+  (js2-consume-token)
+  (let ((pos js2-token-beg)
+        expr
+        pn)
+    (if (= (js2-peek-token) js2-LP)
+        ;; let expression in statement context
+        (setq expr (js2-parse-let pos 'statement)
+              pn (js2-wrap-with-expr-stmt pos expr t))
+      ;; else we're looking at a statement like let x=6, y=7;
+      (setf expr (js2-parse-variables js2-LET pos)
+            pn (js2-wrap-with-expr-stmt pos expr t)
+            (js2-node-type pn) js2-EXPR_RESULT))
+    pn))
+
+(defun js2-parse-ret-yield ()
+  (js2-parse-return-or-yield (js2-peek-token) nil))
+
+(defconst js2-parse-return-stmt-enders
+  (list js2-SEMI js2-RC js2-EOF js2-EOL js2-ERROR js2-RB js2-RP js2-YIELD))
+
+(defsubst js2-now-all-set (before after mask)
+  &quot;Return whether or not the bits in the mask have changed to all set.
+BEFORE is bits before change, AFTER is bits after change, and MASK is
+the mask for bits.  Returns t if all the bits in the mask are set in AFTER
+but not BEFORE.&quot;
+  (and (/= (logand before mask) mask)
+       (= (logand after mask) mask)))
+
+(defun js2-parse-return-or-yield (tt expr-context)
+  (let ((pos js2-token-beg)
+        (end js2-token-end)
+        (before js2-end-flags)
+        (inside-function (js2-inside-function))
+        e
+        ret
+        name)
+    (unless inside-function
+      (js2-report-error (if (eq tt js2-RETURN)
+                            &quot;msg.bad.return&quot;
+                          &quot;msg.bad.yield&quot;)))
+    (js2-consume-token)
+    ;; This is ugly, but we don't want to require a semicolon.
+    (unless (memq (js2-peek-token-or-eol) js2-parse-return-stmt-enders)
+      (setq e (js2-parse-expr)
+            end (js2-node-end e)))
+    (cond
+     ((eq tt js2-RETURN)
+      (js2-set-flag js2-end-flags (if (null e)
+                                      js2-end-returns
+                                    js2-end-returns-value))
+      (setq ret (make-js2-return-node :pos pos
+                                      :len (- end pos)
+                                      :retval e))
+      (js2-node-add-children ret e)
+      ;; See if we need a strict mode warning.
+      ;; TODO:  The analysis done by `js2-has-consistent-return-usage' is
+      ;; more thorough and accurate than this before/after flag check.
+      ;; E.g. if there's a finally-block that always returns, we shouldn't
+      ;; show a warning generated by inconsistent returns in the catch blocks.
+      ;; Basically `js2-has-consistent-return-usage' needs to keep more state,
+      ;; so we know which returns/yields to highlight, and we should get rid of
+      ;; all the checking in `js2-parse-return-or-yield'.
+      (if (and js2-strict-inconsistent-return-warning
+               (js2-now-all-set before js2-end-flags
+                                (logior js2-end-returns js2-end-returns-value)))
+          (js2-add-strict-warning &quot;msg.return.inconsistent&quot; nil pos end)))
+     (t
+      (unless (js2-inside-function)
+        (js2-report-error &quot;msg.bad.yield&quot;))
+      (js2-set-flag js2-end-flags js2-end-yields)
+      (setq ret (make-js2-yield-node :pos pos
+                                     :len (- end pos)
+                                     :value e))
+      (js2-node-add-children ret e)
+      (unless expr-context
+        (setq e ret
+              ret (js2-wrap-with-expr-stmt pos e t))
+      (js2-set-requires-activation)
+      (js2-set-is-generator))))
+    ;; see if we are mixing yields and value returns.
+    (when (and inside-function
+               (js2-now-all-set before js2-end-flags
+                                (logior js2-end-yields js2-end-returns-value)))
+      (setq name (js2-function-name js2-current-script-or-fn))
+      (if (zerop (length name))
+          (js2-report-error &quot;msg.anon.generator.returns&quot; nil pos (- end pos))
+        (js2-report-error &quot;msg.generator.returns&quot; name pos (- end pos))))
+    ret))
+
+(defun js2-parse-debugger ()
+  (js2-consume-token)
+  (make-js2-keyword-node :type js2-DEBUGGER))
+
+(defun js2-parse-block ()
+  &quot;Parser for a curly-delimited statement block.
+Last token matched must be js2-LC.&quot;
+  (let ((pos js2-token-beg)
+        (pn (make-js2-scope)))
+    (js2-consume-token)
+    (js2-push-scope pn)
+    (unwind-protect
+        (progn
+          (js2-parse-statements pn)
+          (js2-must-match js2-RC &quot;msg.no.brace.block&quot;)
+          (setf (js2-node-len pn) (- js2-token-end pos)))
+      (js2-pop-scope))
+    pn))
+
+;; for js2-ERROR too, to have a node for error recovery to work on
+(defun js2-parse-semi ()
+  &quot;Parse a statement or handle an error.
+Last matched token is js-SEMI or js-ERROR.&quot;
+  (let ((tt (js2-peek-token)) pos len)
+    (js2-consume-token)
+    (if (eq tt js2-SEMI)
+        (make-js2-empty-expr-node :len 1)
+      (setq pos js2-token-beg
+            len (- js2-token-beg pos))
+      (js2-report-error &quot;msg.syntax&quot; nil pos len)
+      (make-js2-error-node :pos pos :len len))))
+
+(defun js2-parse-default-xml-namespace ()
+  &quot;Parse a `default xml namespace = &lt;expr&gt;' e4x statement.&quot;
+  (let ((pos js2-token-beg)
+        end len expr unary es)
+    (js2-consume-token)
+    (js2-must-have-xml)
+    (js2-set-requires-activation)
+    (setq len (- js2-ts-cursor pos))
+    (unless (and (js2-match-token js2-NAME)
+                 (string= js2-ts-string &quot;xml&quot;))
+      (js2-report-error &quot;msg.bad.namespace&quot; nil pos len))
+    (unless (and (js2-match-token js2-NAME)
+                 (string= js2-ts-string &quot;namespace&quot;))
+      (js2-report-error &quot;msg.bad.namespace&quot; nil pos len))
+    (unless (js2-match-token js2-ASSIGN)
+      (js2-report-error &quot;msg.bad.namespace&quot; nil pos len))
+    (setq expr (js2-parse-expr)
+          end (js2-node-end expr)
+          unary (make-js2-unary-node :type js2-DEFAULTNAMESPACE
+                                     :pos pos
+                                     :len (- end pos)
+                                     :operand expr))
+    (js2-node-add-children unary expr)
+    (make-js2-expr-stmt-node :pos pos
+                             :len (- end pos)
+                             :expr unary)))
+
+(defun js2-record-label (label bundle)
+  ;; current token should be colon that `js2-parse-primary-expr' left untouched
+  (js2-consume-token)
+  (let ((name (js2-label-node-name label))
+        labeled-stmt
+        dup)
+    (when (setq labeled-stmt (cdr (assoc name js2-label-set)))
+      ;; flag both labels if possible when used in editing mode
+      (if (and js2-parse-ide-mode
+               (setq dup (js2-get-label-by-name labeled-stmt name)))
+          (js2-report-error &quot;msg.dup.label&quot; nil
+                            (js2-node-abs-pos dup) (js2-node-len dup)))
+      (js2-report-error &quot;msg.dup.label&quot; nil
+                        (js2-node-pos label) (js2-node-len label)))
+    (js2-labeled-stmt-node-add-label bundle label)
+    (js2-node-add-children bundle label)
+    ;; Add one reference to the bundle per label in `js2-label-set'
+    (push (cons name bundle) js2-label-set)))
+
+(defun js2-parse-name-or-label ()
+  &quot;Parser for identifier or label.  Last token matched must be js2-NAME.
+Called when we found a name in a statement context.  If it's a label, we gather
+up any following labels and the next non-label statement into a
+`js2-labeled-stmt-node' bundle and return that.  Otherwise we parse an
+expression and return it wrapped in a `js2-expr-stmt-node'.&quot;
+  (let ((pos js2-token-beg)
+        (end js2-token-end)
+        expr
+        stmt
+        pn
+        bundle
+        (continue t))
+    ;; set check for label and call down to `js2-parse-primary-expr'
+    (js2-set-check-for-label)
+    (setq expr (js2-parse-expr))
+    (if (/= (js2-node-type expr) js2-LABEL)
+        ;; Parsed non-label expression - wrap with expression stmt.
+        (setq pn (js2-wrap-with-expr-stmt pos expr t))
+      ;; else parsed a label
+      (setq bundle (make-js2-labeled-stmt-node :pos pos))
+      (js2-record-label expr bundle)
+      ;; look for more labels
+      (while (and continue (= (js2-peek-token) js2-NAME))
+        (js2-set-check-for-label)
+        (setq expr (js2-parse-expr))
+        (if (/= (js2-node-type expr) js2-LABEL)
+            (progn
+              (setq stmt (js2-wrap-with-expr-stmt (js2-node-pos expr) expr t)
+                    continue nil)
+              (js2-auto-insert-semicolon stmt))
+          (js2-record-label expr bundle)))
+      ;; no more labels; now parse the labeled statement
+      (unwind-protect
+            (unless stmt
+              (let ((js2-labeled-stmt bundle))  ; bind dynamically
+                (setq stmt (js2-statement-helper))))
+        ;; remove the labels for this statement from the global set
+        (dolist (label (js2-labeled-stmt-node-labels bundle))
+          (setq js2-label-set (remove label js2-label-set))))
+      (setf (js2-labeled-stmt-node-stmt bundle) stmt
+            (js2-node-len bundle) (- (js2-node-end stmt) pos))
+      (js2-node-add-children bundle stmt)
+      bundle)))
+
+(defun js2-parse-expr-stmt ()
+  &quot;Default parser in statement context, if no recognized statement found.&quot;
+  (js2-wrap-with-expr-stmt js2-token-beg (js2-parse-expr) t))
+
+(defun js2-parse-variables (decl-type pos)
+  &quot;Parse a comma-separated list of variable declarations.
+Could be a 'var', 'const' or 'let' expression, possibly in a for-loop initializer.
+
+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
+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.&quot;
+  (let* ((result (make-js2-var-decl-node :decl-type decl-type
+                                         :pos pos))
+         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;
+    (while continue
+      (setq destructuring nil
+            name nil
+            tt (js2-peek-token)
+            kid-pos js2-token-beg
+            end js2-token-end
+            init nil)
+      (if (or (= tt js2-LB) (= tt js2-LC))
+          ;; Destructuring assignment, e.g., var [a, b] = ...
+          (setq destructuring (js2-parse-primary-expr)
+                end (js2-node-end destructuring))
+        ;; Simple variable name
+        (when (js2-must-match js2-NAME &quot;msg.bad.var&quot;)
+          (setq name (js2-create-name-node)
+                nbeg js2-token-beg
+                nend js2-token-end
+                end nend)
+          (js2-define-symbol decl-type js2-ts-string name js2-in-for-init)))
+      (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)))
+      (when name
+        (js2-set-face nbeg nend (if (js2-function-node-p init)
+                                    'font-lock-function-name-face
+                                  'font-lock-variable-name-face)
+                      'record))
+      (setq vi (make-js2-var-init-node :pos kid-pos
+                                       :len (- end kid-pos)
+                                       :type decl-type))
+      (if destructuring
+          (progn
+            (if (and (null init) (not js2-in-for-init))
+                (js2-report-error &quot;msg.destruct.assign.no.init&quot;))
+            (setf (js2-var-init-node-target vi) destructuring))
+        (setf (js2-var-init-node-target vi) name))
+      (setf (js2-var-init-node-initializer vi) init)
+      (js2-node-add-children vi name destructuring init)
+      (js2-block-node-push result vi)
+      (unless (js2-match-token js2-COMMA)
+        (setq continue nil)))
+    (setf (js2-node-len result) (- end pos))
+    result))
+
+(defun js2-parse-let (pos &amp;optional stmt-p)
+  &quot;Parse a let expression or statement.
+A let-expression is of the form `let (vars) expr'.
+A let-statment is of the form `let (vars) {statements}'.
+The third form of let is a variable declaration list, handled
+by `js2-parse-variables'.&quot;
+  (let ((pn (make-js2-let-node :pos pos))
+        beg vars body)
+    (if (js2-must-match js2-LP &quot;msg.no.paren.after.let&quot;)
+        (setf (js2-let-node-lp pn) (- js2-token-beg pos)))
+    (js2-push-scope pn)
+    (unwind-protect
+        (progn
+          (setq vars (js2-parse-variables js2-LET js2-token-beg))
+          (if (js2-must-match js2-RP &quot;msg.no.paren.let&quot;)
+              (setf (js2-let-node-rp pn) (- js2-token-beg pos)))
+          (if (and stmt-p (eq (js2-peek-token) js2-LC))
+              ;; let statement
+              (progn
+                (js2-consume-token)
+                (setf beg js2-token-beg  ; position stmt at LC
+                      body (js2-parse-statements))
+                (js2-must-match js2-RC &quot;msg.no.curly.let&quot;)
+                (setf (js2-node-len body) (- js2-token-end beg)
+                      (js2-node-len pn) (- js2-token-end pos)
+                      (js2-let-node-body pn) body
+                      (js2-node-type pn) js2-LET))
+            ;; let expression
+            (setf body (js2-parse-expr)
+                  (js2-node-len pn) (- (js2-node-end body) pos)
+                  (js2-let-node-body pn) body))
+          (js2-node-add-children pn vars body))
+      (js2-pop-scope))
+    pn))
+
+(defsubst js2-define-new-symbol (decl-type name node)
+  (js2-scope-put-symbol js2-current-scope
+                        name
+                        (make-js2-symbol decl-type name node)))
+
+(defun js2-define-symbol (decl-type name &amp;optional node ignore-not-in-block)
+  &quot;Define a symbol in the current scope.
+If NODE is non-nil, it is the AST node associated with the symbol.&quot;
+  (let* ((defining-scope (js2-get-defining-scope js2-current-scope name))
+         (symbol (if defining-scope
+                     (js2-scope-get-symbol defining-scope name)))
+         (sdt (if symbol (js2-symbol-decl-type symbol) -1)))
+    (cond
+     ((and symbol ; already defined
+           (or (= sdt js2-CONST) ; old version is const
+               (= decl-type js2-CONST) ; new version is const
+               ;; two let-bound vars in this block have same name
+               (and (= sdt js2-LET)
+                    (eq defining-scope js2-current-scope))))
+      (js2-report-error
+       (cond
+        ((= sdt js2-CONST) &quot;msg.const.redecl&quot;)
+        ((= sdt js2-LET) &quot;msg.let.redecl&quot;)
+        ((= sdt js2-VAR) &quot;msg.var.redecl&quot;)
+        ((= sdt js2-FUNCTION) &quot;msg.function.redecl&quot;)
+        (t &quot;msg.parm.redecl&quot;))
+       name))
+     ((= decl-type js2-LET)
+      (if (and (not ignore-not-in-block)
+               (or (= (js2-node-type js2-current-scope) js2-IF)
+                   (js2-loop-node-p js2-current-scope)))
+          (js2-report-error &quot;msg.let.decl.not.in.block&quot;)
+        (js2-define-new-symbol decl-type name node)))
+     ((or (= decl-type js2-VAR)
+          (= decl-type js2-CONST)
+          (= decl-type js2-FUNCTION))
+      (if symbol
+          (if (and js2-strict-var-redeclaration-warning (= sdt js2-VAR))
+              (js2-add-strict-warning &quot;msg.var.redecl&quot; name)
+            (if (and js2-strict-var-hides-function-arg-warning (= sdt js2-LP))
+                (js2-add-strict-warning &quot;msg.var.hides.arg&quot; name)))
+        (js2-define-new-symbol decl-type name node)))
+     ((= decl-type js2-LP)
+      (if symbol
+          ;; must be duplicate parameter. Second parameter hides the
+          ;; first, so go ahead and add the second pararameter
+          (js2-report-warning &quot;msg.dup.parms&quot; name))
+      (js2-define-new-symbol decl-type name node))
+     (t (js2-code-bug)))))
+
+(defun js2-parse-expr ()
+  (let* ((pn (js2-parse-assign-expr))
+         (pos (js2-node-pos pn))
+         left
+         right
+         op-pos)
+    (while (js2-match-token js2-COMMA)
+      (setq op-pos (- js2-token-beg pos))  ; relative
+      (if (= (js2-peek-token) js2-YIELD)
+          (js2-report-error &quot;msg.yield.parenthesized&quot;))
+      (setq right (js2-parse-assign-expr)
+            left pn
+            pn (make-js2-infix-node :type js2-COMMA
+                                    :pos pos
+                                    :len (- js2-ts-cursor pos)
+                                    :op-pos op-pos
+                                    :left left
+                                    :right right))
+      (js2-node-add-children pn left right))
+    pn))
+
+(defun js2-parse-assign-expr ()
+  (let ((tt (js2-peek-token))
+        (pos js2-token-beg)
+        pn
+        left
+        right
+        op-pos)
+    (if (= tt js2-YIELD)
+        (js2-parse-return-or-yield tt t)
+      ;; not yield - parse assignment expression
+      (setq pn (js2-parse-cond-expr)
+            tt (js2-peek-token))
+      (when (and (&lt;= js2-first-assign tt)
+                 (&lt;= tt js2-last-assign))
+        (js2-consume-token)
+        (setq op-pos (- js2-token-beg pos)  ; relative
+              left pn
+              right (js2-parse-assign-expr)
+              pn (make-js2-assign-node :type tt
+                                       :pos pos
+                                       :len (- (js2-node-end right) pos)
+                                       :op-pos op-pos
+                                       :left left
+                                       :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)))
+        ;; do this last so ide checks above can use absolute positions
+        (js2-node-add-children pn left right))
+      pn)))
+
+(defun js2-parse-cond-expr ()
+  (let ((pos js2-token-beg)
+        (pn (js2-parse-or-expr))
+        test-expr
+        if-true
+        if-false
+        q-pos
+        c-pos)
+    (when (js2-match-token js2-HOOK)
+      (setq q-pos (- js2-token-beg pos)
+            if-true (js2-parse-assign-expr))
+      (js2-must-match js2-COLON &quot;msg.no.colon.cond&quot;)
+      (setq c-pos (- js2-token-beg pos)
+            if-false (js2-parse-assign-expr)
+            test-expr pn
+            pn (make-js2-cond-node :pos pos
+                                   :len (- (js2-node-end if-false) pos)
+                                   :test-expr test-expr
+                                   :true-expr if-true
+                                   :false-expr if-false
+                                   :q-pos q-pos
+                                   :c-pos c-pos))
+      (js2-node-add-children pn test-expr if-true if-false))
+    pn))
+
+(defun js2-make-binary (type left parser)
+  &quot;Helper for constructing a binary-operator AST node.
+LEFT is the left-side-expression, already parsed, and the
+binary operator should have just been matched.
+PARSER is a function to call to parse the right operand,
+or a `js2-node' struct if it has already been parsed.&quot;
+  (let* ((pos (js2-node-pos left))
+         (op-pos (- js2-token-beg pos))
+         (right (if (js2-node-p parser)
+                    parser
+                  (funcall parser)))
+         (pn (make-js2-infix-node :type type
+                                  :pos pos
+                                  :len (- (js2-node-end right) pos)
+                                  :op-pos op-pos
+                                  :left left
+                                  :right right)))
+    (js2-node-add-children pn left right)
+    pn))
+
+(defun js2-parse-or-expr ()
+  (let ((pn (js2-parse-and-expr)))
+    (when (js2-match-token js2-OR)
+      (setq pn (js2-make-binary js2-OR
+                                pn
+                                'js2-parse-or-expr)))
+    pn))
+
+(defun js2-parse-and-expr ()
+  (let ((pn (js2-parse-bit-or-expr)))
+    (when (js2-match-token js2-AND)
+      (setq pn (js2-make-binary js2-AND
+                                pn
+                                'js2-parse-and-expr)))
+    pn))
+
+(defun js2-parse-bit-or-expr ()
+  (let ((pn (js2-parse-bit-xor-expr)))
+    (while (js2-match-token js2-BITOR)
+      (setq pn (js2-make-binary js2-BITOR
+                                pn
+                                'js2-parse-bit-xor-expr)))
+    pn))
+
+(defun js2-parse-bit-xor-expr ()
+  (let ((pn (js2-parse-bit-and-expr)))
+    (while (js2-match-token js2-BITXOR)
+      (setq pn (js2-make-binary js2-BITXOR
+                                pn
+                                'js2-parse-bit-and-expr)))
+    pn))
+
+(defun js2-parse-bit-and-expr ()
+  (let ((pn (js2-parse-eq-expr)))
+    (while (js2-match-token js2-BITAND)
+      (setq pn (js2-make-binary js2-BITAND
+                                pn
+                                'js2-parse-eq-expr)))
+    pn))
+
+(defconst js2-parse-eq-ops
+  (list js2-EQ js2-NE js2-SHEQ js2-SHNE))
+
+(defun js2-parse-eq-expr ()
+  (let ((pn (js2-parse-rel-expr))
+        tt)
+    (while (memq (setq tt (js2-peek-token)) js2-parse-eq-ops)
+      (js2-consume-token)
+      (setq pn (js2-make-binary tt
+                                pn
+                                'js2-parse-rel-expr)))
+    pn))
+
+(defconst js2-parse-rel-ops
+  (list js2-IN js2-INSTANCEOF js2-LE js2-LT js2-GE js2-GT))
+
+(defun js2-parse-rel-expr ()
+  (let ((pn (js2-parse-shift-expr))
+        (continue t)
+        tt)
+    (while continue
+      (setq tt (js2-peek-token))
+      (cond
+       ((and js2-in-for-init (= tt js2-IN))
+        (setq continue nil))
+       ((memq tt js2-parse-rel-ops)
+        (js2-consume-token)
+        (setq pn (js2-make-binary tt pn 'js2-parse-shift-expr)))
+       (t
+        (setq continue nil))))
+    pn))
+
+(defconst js2-parse-shift-ops
+  (list js2-LSH js2-URSH js2-RSH))
+
+(defun js2-parse-shift-expr ()
+  (let ((pn (js2-parse-add-expr))
+        tt
+        (continue t))
+    (while continue
+      (setq tt (js2-peek-token))
+      (if (memq tt js2-parse-shift-ops)
+          (progn
+            (js2-consume-token)
+            (setq pn (js2-make-binary tt pn 'js2-parse-add-expr)))
+        (setq continue nil)))
+    pn))
+
+(defun js2-parse-add-expr ()
+  (let ((pn (js2-parse-mul-expr))
+        tt
+        (continue t))
+    (while continue
+      (setq tt (js2-peek-token))
+      (if (or (= tt js2-ADD) (= tt js2-SUB))
+          (progn
+            (js2-consume-token)
+            (setq pn (js2-make-binary tt pn 'js2-parse-mul-expr)))
+        (setq continue nil)))
+    pn))
+
+(defconst js2-parse-mul-ops
+  (list js2-MUL js2-DIV js2-MOD))
+
+(defun js2-parse-mul-expr ()
+  (let ((pn (js2-parse-unary-expr))
+        tt
+        (continue t))
+    (while continue
+      (setq tt (js2-peek-token))
+      (if (memq tt js2-parse-mul-ops)
+          (progn
+            (js2-consume-token)
+            (setq pn (js2-make-binary tt pn 'js2-parse-unary-expr)))
+        (setq continue nil)))
+    pn))
+
+(defsubst js2-make-unary (type parser &amp;rest args)
+  &quot;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).&quot;
+  (let* ((pos js2-token-beg)
+         (postfix (js2-node-p parser))
+         (expr (if postfix
+                   parser
+                 (apply parser args)))
+         end
+         pn)
+    (if postfix  ; e.g. i++
+        (setq pos (js2-node-pos expr)
+              end js2-token-end)
+      (setq end (js2-node-end expr)))
+    (setq pn (make-js2-unary-node :type type
+                                  :pos pos
+                                  :len (- end pos)
+                                  :operand expr))
+    (js2-node-add-children pn expr)
+    pn))
+
+(defconst js2-incrementable-node-types
+  (list js2-NAME js2-GETPROP js2-GETELEM js2-GET_REF js2-CALL)
+  &quot;Node types that can be the operand of a ++ or -- operator.&quot;)
+
+(defsubst 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)
+                          &quot;msg.bad.incr&quot;
+                        &quot;msg.bad.decr&quot;)
+                      nil beg (- end beg))))
+
+(defun js2-parse-unary-expr ()
+  (let ((tt (js2-peek-token))
+        pn expr beg end)
+    (cond
+     ((or (= tt js2-VOID)
+          (= tt js2-NOT)
+          (= tt js2-BITNOT)
+          (= tt js2-TYPEOF))
+      (js2-consume-token)
+      (js2-make-unary tt 'js2-parse-unary-expr))
+     ((= tt js2-ADD)
+      (js2-consume-token)
+      ;; Convert to special POS token in decompiler and parse tree
+      (js2-make-unary js2-POS 'js2-parse-unary-expr))
+     ((= tt js2-SUB)
+      (js2-consume-token)
+      ;; Convert to special NEG token in decompiler and parse tree
+      (js2-make-unary js2-NEG 'js2-parse-unary-expr))
+     ((or (= tt js2-INC)
+          (= tt js2-DEC))
+      (js2-consume-token)
+      (prog1
+          (setq beg js2-token-beg
+                end js2-token-end
+                expr (js2-make-unary tt 'js2-parse-member-expr t))
+        (js2-check-bad-inc-dec tt beg end expr)))
+     ((= tt js2-DELPROP)
+      (js2-consume-token)
+      (js2-make-unary js2-DELPROP 'js2-parse-unary-expr))
+     ((= tt js2-ERROR)
+      (js2-consume-token)
+      (make-js2-error-node))  ; try to continue
+     ((and (= tt js2-LT)
+           js2-compiler-xml-available)
+      ;; XML stream encountered in expression.
+      (js2-consume-token)
+      (js2-parse-member-expr-tail t (js2-parse-xml-initializer)))
+     (t
+      (setq pn (js2-parse-member-expr t)
+            ;; Don't look across a newline boundary for a postfix incop.
+            tt (js2-peek-token-or-eol))
+      (when (or (= tt js2-INC) (= tt js2-DEC))
+        (js2-consume-token)
+        (setf expr pn
+              pn (js2-make-unary tt expr))
+        (js2-node-set-prop pn 'postfix t)
+        (js2-check-bad-inc-dec tt js2-token-beg js2-token-end pn))
+      pn))))
+
+(defun js2-parse-xml-initializer ()
+  &quot;Parse an E4X XML initializer.
+I'm parsing it the way Rhino parses it, but without the tree-rewriting.
+Then I'll postprocess the result, depending on whether we're in IDE
+mode or codegen mode, and generate the appropriate rewritten AST.
+IDE mode uses a rich AST that models the XML structure.  Codegen mode
+just concatenates everything and makes a new XML or XMLList out of it.&quot;
+  (let ((tt (js2-get-first-xml-token))
+        pn-xml
+        pn
+        expr
+        kids
+        expr-pos
+        (continue t)
+        (first-token t))
+    (when (not (or (= tt js2-XML) (= tt js2-XMLEND)))
+      (js2-report-error &quot;msg.syntax&quot;))
+    (setq pn-xml (make-js2-xml-node))
+    (while continue
+      (if first-token
+          (setq first-token nil)
+        (setq tt (js2-get-next-xml-token)))
+      (cond
+       ;; js2-XML means we found a {expr} in the XML stream.
+       ;; The js2-ts-string is the XML up to the left-curly.
+       ((= tt js2-XML)
+        (push (make-js2-string-node :pos js2-token-beg
+                                    :len (- js2-ts-cursor js2-token-beg))
+              kids)
+        (js2-must-match js2-LC &quot;msg.syntax&quot;)
+        (setq expr-pos js2-ts-cursor
+              expr (if (eq (js2-peek-token) js2-RC)
+                       (make-js2-empty-expr-node :pos expr-pos)
+                     (js2-parse-expr)))
+        (js2-must-match js2-RC &quot;msg.syntax&quot;)
+        (setq pn (make-js2-xml-js-expr-node :pos (js2-node-pos expr)
+                                            :len (js2-node-len expr)
+                                            :expr expr))
+        (js2-node-add-children pn expr)
+        (push pn kids))
+       ;; a js2-XMLEND token means we hit the final close-tag.
+       ((= tt js2-XMLEND)
+        (push (make-js2-string-node :pos js2-token-beg
+                                    :len (- js2-ts-cursor js2-token-beg))
+              kids)
+        (dolist (kid (nreverse kids))
+          (js2-block-node-push pn-xml kid))
+        (setf (js2-node-len pn-xml) (- js2-ts-cursor
+                                       (js2-node-pos pn-xml))
+              continue nil))
+       (t
+        (js2-report-error &quot;msg.syntax&quot;)
+        (setq continue nil))))
+    pn-xml))
+
+
+(defun js2-parse-argument-list ()
+  &quot;Parse an argument list and return it as a lisp list of nodes.
+Returns the list in reverse order.  Consumes the right-paren token.&quot;
+  (let (result)
+    (unless (js2-match-token js2-RP)
+      (loop do
+            (if (= (js2-peek-token) js2-YIELD)
+                (js2-report-error &quot;msg.yield.parenthesized&quot;))
+            (push (js2-parse-assign-expr) result)
+            while
+            (js2-match-token js2-COMMA))
+      (js2-must-match js2-RP &quot;msg.no.paren.arg&quot;)
+      result)))
+
+(defun js2-parse-member-expr (&amp;optional allow-call-syntax)
+  (let ((tt (js2-peek-token))
+        pn
+        pos
+        target
+        args
+        beg
+        end
+        init
+        tail)
+    (if (/= tt js2-NEW)
+        (setq pn (js2-parse-primary-expr))
+      ;; parse a 'new' expression
+      (js2-consume-token)
+      (setq pos js2-token-beg
+            beg pos
+            target (js2-parse-member-expr)
+            end (js2-node-end target)
+            pn (make-js2-new-node :pos pos
+                                  :target target
+                                  :len (- end pos)))
+      (js2-node-add-children pn target)
+      (when (js2-match-token js2-LP)
+        ;; Add the arguments to pn, if any are supplied.
+        (setf beg pos  ; start of &quot;new&quot; keyword
+              pos js2-token-beg
+              args (nreverse (js2-parse-argument-list))
+              (js2-new-node-args pn) args
+              end js2-token-end
+              (js2-new-node-lp pn) (- pos beg)
+              (js2-new-node-rp pn) (- end 1 beg))
+        (apply #'js2-node-add-children pn args))
+      (when (and js2-allow-rhino-new-expr-initializer
+                 (js2-match-token js2-LC))
+        (setf init (js2-parse-object-literal)
+              end (js2-node-end init)
+              (js2-new-node-initializer pn) init)
+        (js2-node-add-children pn init))
+        (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)
+  &quot;Parse a chain of property/array accesses or function calls.
+Includes parsing for E4X operators like `..' and `.@'.
+If ALLOW-CALL-SYNTAX is nil, stops when we encounter a left-paren.
+Returns an expression tree that includes PN, the parent node.&quot;
+  (let ((beg (js2-node-pos pn))
+        tt
+        (continue t))
+    (while continue
+      (setq tt (js2-peek-token))
+      (cond
+       ((or (= tt js2-DOT) (= tt js2-DOTDOT))
+        (setq pn (js2-parse-property-access tt pn)))
+       ((= tt js2-DOTQUERY)
+        (setq pn (js2-parse-dot-query pn)))
+       ((= tt js2-LB)
+        (setq pn (js2-parse-element-get pn)))
+       ((= tt js2-LP)
+        (if allow-call-syntax
+            (setq pn (js2-parse-function-call pn))
+          (setq continue nil)))
+       (t
+        (setq continue nil))))
+    (if (&gt;= js2-highlight-level 2)
+        (js2-parse-highlight-member-expr-node pn))
+    pn))
+
+(defun js2-parse-dot-query (pn)
+  &quot;Parse a dot-query expression, e.g. foo.bar.(@name == 2)
+Last token parsed must be `js2-DOTQUERY'.&quot;
+  (let ((pos (js2-node-pos pn))
+        op-pos
+        expr
+        end)
+    (js2-consume-token)
+    (js2-must-have-xml)
+    (js2-set-requires-activation)
+    (setq op-pos js2-token-beg
+          expr (js2-parse-expr)
+          end (js2-node-end expr)
+          pn (make-js2-xml-dot-query-node :left pn
+                                          :pos pos
+                                          :op-pos op-pos
+                                          :right expr))
+    (js2-node-add-children pn
+                           (js2-xml-dot-query-node-left pn)
+                           (js2-xml-dot-query-node-right pn))
+    (if (js2-must-match js2-RP &quot;msg.no.paren&quot;)
+        (setf (js2-xml-dot-query-node-rp pn) js2-token-beg
+              end js2-token-end))
+    (setf (js2-node-len pn) (- end pos))
+    pn))
+
+(defun js2-parse-element-get (pn)
+  &quot;Parse an element-get expression, e.g. foo[bar].
+Last token parsed must be `js2-RB'.&quot;
+  (let ((lb js2-token-beg)
+        (pos (js2-node-pos pn))
+        rb
+        expr)
+    (js2-consume-token)
+    (setq expr (js2-parse-expr))
+    (if (js2-must-match js2-RB &quot;msg.no.bracket.index&quot;)
+        (setq rb js2-token-beg))
+    (setq pn (make-js2-elem-get-node :target pn
+                                     :pos pos
+                                     :element expr
+                                     :lb (js2-relpos lb pos)
+                                     :rb (js2-relpos rb pos)
+                                     :len (- js2-token-end pos)))
+    (js2-node-add-children pn
+                           (js2-elem-get-node-target pn)
+                           (js2-elem-get-node-element pn))
+    pn))
+
+(defun js2-parse-function-call (pn)
+  (let (args
+        (pos (js2-node-pos pn)))
+    (js2-consume-token)
+    (setq pn (make-js2-call-node :pos pos
+                                 :target pn
+                                 :lp (- js2-token-beg pos)))
+    (js2-node-add-children pn (js2-call-node-target pn))
+    ;; Add the arguments to pn, if any are supplied.
+    (setf args (nreverse (js2-parse-argument-list))
+          (js2-call-node-rp pn) (- js2-token-beg pos)
+          (js2-call-node-args pn) args)
+    (apply #'js2-node-add-children pn args)
+    (setf (js2-node-len pn) (- js2-ts-cursor pos))
+    pn))
+
+(defun js2-parse-property-access (tt pn)
+  &quot;Parse a property access, XML descendants access, or XML attr access.&quot;
+  (let ((member-type-flags 0)
+        (dot-pos js2-token-beg)
+        (dot-len (if (= tt js2-DOTDOT) 2 1))
+        name
+        ref  ; right side of . or .. operator
+        result)
+    (js2-consume-token)
+    (when (= tt js2-DOTDOT)
+      (js2-must-have-xml)
+      (setq member-type-flags js2-descendants-flag))
+    (if (not js2-compiler-xml-available)
+        (progn
+          (js2-must-match-prop-name &quot;msg.no.name.after.dot&quot;)
+          (setq name (js2-create-name-node t js2-GETPROP)
+                result (make-js2-prop-get-node :left pn
+                                               :pos js2-token-beg
+                                               :right name
+                                               :len (- js2-token-end
+                                                       js2-token-beg)))
+          (js2-node-add-children result pn name)
+          result)
+      ;; otherwise look for XML operators
+      (setf result (if (= tt js2-DOT)
+                       (make-js2-prop-get-node)
+                     (make-js2-infix-node :type js2-DOTDOT))
+            (js2-node-pos result) (js2-node-pos pn)
+            (js2-infix-node-op-pos result) dot-pos
+            (js2-infix-node-left result) pn  ; do this after setting position
+            tt (js2-next-token))
+      (cond
+       ;; needed for generator.throw()
+       ((= tt js2-THROW)
+        (js2-save-name-token-data js2-token-beg &quot;throw&quot;)
+        (setq ref (js2-parse-property-name nil js2-ts-string member-type-flags)))
+       ;; handles: name, ns::name, ns::*, ns::[expr]
+       ((js2-valid-prop-name-token tt)
+        (setq ref (js2-parse-property-name -1 js2-ts-string member-type-flags)))
+       ;; handles: *, *::name, *::*, *::[expr]
+       ((= tt js2-MUL)
+        (js2-save-name-token-data js2-token-beg &quot;*&quot;)
+        (setq ref (js2-parse-property-name nil &quot;*&quot; member-type-flags)))
+       ;; handles: '@attr', '@ns::attr', '@ns::*', '@ns::[expr]', etc.
+       ((= tt js2-XMLATTR)
+        (setq result (js2-parse-attribute-access)))
+       (t
+        (js2-report-error &quot;msg.no.name.after.dot&quot; nil dot-pos dot-len)))
+      (if ref
+          (setf (js2-node-len result) (- (js2-node-end ref)
+                                         (js2-node-pos result))
+                (js2-infix-node-right result) ref))
+      (if (js2-infix-node-p result)
+          (js2-node-add-children result
+                                 (js2-infix-node-left result)
+                                 (js2-infix-node-right result)))
+      result)))
+
+(defun js2-parse-attribute-access ()
+  &quot;Parse an E4X XML attribute expression.
+This includes expressions of the forms:
+
+  @attr      @ns::attr     @ns::*
+  @*         @*::attr      @*::*
+  @[expr]    @*::[expr]    @ns::[expr]
+
+Called if we peeked an '@' token.&quot;
+  (let ((tt (js2-next-token))
+        (at-pos js2-token-beg))
+    (cond
+     ;; handles: @name, @ns::name, @ns::*, @ns::[expr]
+     ((js2-valid-prop-name-token tt)
+      (js2-parse-property-name at-pos js2-ts-string 0))
+     ;; handles: @*, @*::name, @*::*, @*::[expr]
+     ((= tt js2-MUL)
+      (js2-save-name-token-data js2-token-beg &quot;*&quot;)
+      (js2-parse-property-name js2-token-beg &quot;*&quot; 0))
+     ;; handles @[expr]
+     ((= tt js2-LB)
+      (js2-parse-xml-elem-ref at-pos))
+     (t
+      (js2-report-error &quot;msg.no.name.after.xmlAttr&quot;)
+      ;; Avoid cascaded errors that happen if we make an error node here.
+      (js2-save-name-token-data js2-token-beg &quot;&quot;)
+      (js2-parse-property-name js2-token-beg &quot;&quot; 0)))))
+
+(defun js2-parse-property-name (at-pos s member-type-flags)
+  &quot;Check if :: follows name in which case it becomes qualified name.
+
+AT-POS is a natural number if we just read an '@' token, else nil.
+S is the name or string that was matched:  an identifier, 'throw' or '*'.
+MEMBER-TYPE-FLAGS is a bit set tracking whether we're a '.' or '..' child.
+
+Returns a `js2-xml-ref-node' if it's an attribute access, a child of a '..'
+operator, or the name is followed by ::.  For a plain name, returns a
+`js2-name-node'.  Returns a `js2-error-node' for malformed XML expressions.&quot;
+  (let ((pos (or at-pos js2-token-beg))
+        colon-pos
+        (name (js2-create-name-node t js2-current-token))
+        ns
+        tt
+        ref
+        pn)
+    (catch 'return
+      (when (js2-match-token js2-COLONCOLON)
+        (setq ns name
+              colon-pos js2-token-beg
+              tt (js2-next-token))
+        (cond
+         ;; handles name::name
+         ((js2-valid-prop-name-token tt)
+          (setq name (js2-create-name-node)))
+         ;; handles name::*
+         ((= tt js2-MUL)
+          (js2-save-name-token-data js2-token-beg &quot;*&quot;)
+          (setq name (js2-create-name-node)))
+         ;; handles name::[expr]
+         ((= tt js2-LB)
+          (throw 'return (js2-parse-xml-elem-ref at-pos ns colon-pos)))
+         (t
+          (js2-report-error &quot;msg.no.name.after.coloncolon&quot;))))
+      (if (and (null ns) (zerop member-type-flags))
+          name
+        (prog1
+            (setq pn
+                  (make-js2-xml-prop-ref-node :pos pos
+                                              :len (- (js2-node-end name) pos)
+                                              :at-pos at-pos
+                                              :colon-pos colon-pos
+                                              :propname name))
+          (js2-node-add-children pn name))))))
+
+(defun js2-parse-xml-elem-ref (at-pos &amp;optional namespace colon-pos)
+  &quot;Parse the [expr] portion of an xml element reference.
+For instance, @[expr], @*::[expr], or ns::[expr].&quot;
+  (let* ((lb js2-token-beg)
+         (pos (or at-pos lb))
+         rb
+         (expr (js2-parse-expr))
+         (end (js2-node-end expr))
+         pn)
+    (if (js2-must-match js2-RB &quot;msg.no.bracket.index&quot;)
+        (setq rb js2-token-beg
+              end js2-token-end))
+    (prog1
+        (setq pn
+              (make-js2-xml-elem-ref-node :pos pos
+                                          :len (- end pos)
+                                          :namespace namespace
+                                          :colon-pos colon-pos
+                                          :at-pos at-pos
+                                          :expr expr
+                                          :lb (js2-relpos lb pos)
+                                          :rb (js2-relpos rb pos)))
+      (js2-node-add-children pn namespace expr))))
+
+(defun js2-parse-primary-expr ()
+  &quot;Parses a literal (leaf) expression of some sort.
+Includes complex literals such as functions, object-literals,
+array-literals, array comprehensions and regular expressions.&quot;
+  (let ((tt-flagged (js2-next-flagged-token))
+        pn      ; parent node  (usually return value)
+        tt
+        px-pos  ; paren-expr pos
+        len
+        flags   ; regexp flags
+        expr)
+    (setq tt js2-current-token)
+    (cond
+     ((= tt js2-FUNCTION)
+      (js2-parse-function 'FUNCTION_EXPRESSION))
+     ((= tt js2-LB)
+      (js2-parse-array-literal))
+     ((= tt js2-LC)
+      (js2-parse-object-literal))
+     ((= tt js2-LET)
+      (js2-parse-let js2-token-beg))
+     ((= tt js2-LP)
+      (setq px-pos js2-token-beg
+            expr (js2-parse-expr))
+      (js2-must-match js2-RP &quot;msg.no.paren&quot;)
+      (setq pn (make-js2-paren-node :pos px-pos
+                                    :expr expr
+                                    :len (- js2-token-end px-pos)))
+      (js2-node-add-children pn (js2-paren-node-expr pn))
+      pn)
+     ((= tt js2-XMLATTR)
+      (js2-must-have-xml)
+      (js2-parse-attribute-access))
+     ((= tt js2-NAME)
+      (js2-parse-name tt-flagged tt))
+     ((= tt js2-NUMBER)
+      (make-js2-number-node))
+     ((= tt js2-STRING)
+      (prog1
+          (make-js2-string-node)
+        (js2-record-face 'font-lock-string-face)))
+     ((or (= tt js2-DIV) (= tt js2-ASSIGN_DIV))
+      ;; Got / or /= which in this context means a regexp literal
+      (setq px-pos js2-token-beg)
+      (js2-read-regexp tt)
+      (setq flags js2-ts-regexp-flags
+            js2-ts-regexp-flags nil)
+      (prog1
+          (make-js2-regexp-node :pos px-pos
+                                :len (- js2-ts-cursor px-pos)
+                                :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))))
+     ((or (= tt js2-NULL)
+          (= tt js2-THIS)
+          (= tt js2-FALSE)
+          (= tt js2-TRUE))
+      (make-js2-keyword-node :type tt))
+     ((= tt js2-RESERVED)
+      (js2-report-error &quot;msg.reserved.id&quot;)
+      (make-js2-name-node))
+     ((= tt js2-ERROR)
+      ;; the scanner or one of its subroutines reported the error.
+      (make-js2-error-node))
+     ((= tt js2-EOF)
+      (setq px-pos (point-at-bol)
+            len (- js2-ts-cursor px-pos))
+      (js2-report-error &quot;msg.unexpected.eof&quot; nil px-pos len)
+      (make-js2-error-node :pos px-pos :len len))
+     (t
+      (js2-report-error &quot;msg.syntax&quot;)
+      (make-js2-error-node)))))
+
+(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
+            ;; 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)))))
+
+(defsubst 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
+   (max (if elems
+            (js2-node-pos (car elems))
+          pos)
+        (save-excursion
+          (goto-char comma-pos)
+          (back-to-indentation)
+          (point)))
+   comma-pos))
+
+(defun js2-parse-array-literal ()
+  (let ((pos js2-token-beg)
+        (end js2-token-end)
+        (after-lb-or-comma t)
+        after-comma
+        tt
+        elems
+        pn
+        (continue t))
+    (while continue
+      (setq tt (js2-peek-token))
+      (cond
+       ;; comma
+       ((= tt js2-COMMA)
+        (js2-consume-token)
+        (setq after-comma js2-token-end)
+        (if (not after-lb-or-comma)
+            (setq after-lb-or-comma t)
+          (push nil elems)))
+       ;; end of array
+       ((or (= tt js2-RB)
+            (= tt js2-EOF))  ; prevent infinite loop
+        (if (= tt js2-EOF)
+            (js2-report-error &quot;msg.no.bracket.arg&quot; nil pos)
+          (js2-consume-token))
+        (setq continue nil
+              end js2-token-end
+              pn (make-js2-array-node :pos pos
+                                      :len (- js2-ts-cursor pos)
+                                      :elems (nreverse elems)))
+        (apply #'js2-node-add-children pn (js2-array-node-elems pn))
+        (when after-comma
+          (js2-parse-warn-trailing-comma &quot;msg.array.trailing.comma&quot;
+                                         pos elems after-comma)))
+       ;; array comp
+       ((and (&gt;= js2-language-version 170)
+             (= tt js2-FOR)          ; check for array comprehension
+             (not after-lb-or-comma) ; &quot;for&quot; can't follow a comma
+             elems                   ; must have at least 1 element
+             (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
+          (js2-report-error &quot;msg.no.bracket.arg&quot;))
+        (push (js2-parse-assign-expr) elems)
+        (setq after-lb-or-comma nil
+              after-comma nil))))
+    pn))
+
+(defun js2-parse-array-comprehension (expr pos)
+  &quot;Parse a JavaScript 1.7 Array Comprehension.
+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.&quot;
+  (let (loops
+        filter
+        if-pos
+        result)
+    (while (= (js2-peek-token) js2-FOR)
+      (push (js2-parse-array-comp-loop) loops))
+    (when (= (js2-peek-token) js2-IF)
+      (js2-consume-token)
+      (setq if-pos (- js2-token-beg pos)  ; relative
+            filter (js2-parse-condition)))
+    (js2-must-match js2-RB &quot;msg.no.bracket.arg&quot; pos)
+    (setq result (make-js2-array-comp-node :pos pos
+                                           :len (- js2-ts-cursor pos)
+                                           :result expr
+                                           :loops (nreverse loops)
+                                           :filter (car filter)
+                                           :lp (js2-relpos (second filter) pos)
+                                           :rp (js2-relpos (third filter) pos)
+                                           :if-pos if-pos))
+    (apply #'js2-node-add-children result expr (car filter)
+           (js2-array-comp-node-loops result))
+    result))
+
+(defun js2-parse-array-comp-loop ()
+  &quot;Parse a 'for [each] (foo in bar)' expression in an Array comprehension.
+Last token peeked should be the initial FOR.&quot;
+  (let ((pos js2-token-beg)
+        (pn (make-js2-array-comp-loop-node))
+        tt
+        iter
+        obj
+        foreach-p
+        in-pos
+        each-pos
+        lp
+        rp)
+    (assert (= (js2-next-token) js2-FOR))  ; consumes token
+    (js2-push-scope pn)
+    (unwind-protect
+        (progn
+          (when (js2-match-token js2-NAME)
+            (if (string= js2-ts-string &quot;each&quot;)
+                (progn
+                  (setq foreach-p t
+                        each-pos (- js2-token-beg pos)) ; relative
+                  (js2-record-face 'font-lock-keyword-face))
+              (js2-report-error &quot;msg.no.paren.for&quot;)))
+          (if (js2-must-match js2-LP &quot;msg.no.paren.for&quot;)
+              (setq lp (- js2-token-beg pos)))
+          (setq tt (js2-peek-token))
+          (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-create-name-node)))
+           (t
+            (js2-report-error &quot;msg.bad.var&quot;)))
+          ;; Define as a let since we want the scope of the variable to
+          ;; be restricted to the array comprehension
+          (if (js2-name-node-p iter)
+              (js2-define-symbol js2-LET (js2-name-node-name iter) pn t))
+          (if (js2-must-match js2-IN &quot;msg.in.after.for.name&quot;)
+              (setq in-pos (- js2-token-beg pos)))
+          (setq obj (js2-parse-expr))
+          (if (js2-must-match js2-RP &quot;msg.no.paren.for.ctrl&quot;)
+              (setq rp (- js2-token-beg pos)))
+          (setf (js2-node-pos pn) pos
+                (js2-node-len pn) (- js2-ts-cursor pos)
+                (js2-array-comp-loop-node-iterator pn) iter
+                (js2-array-comp-loop-node-object pn) obj
+                (js2-array-comp-loop-node-in-pos pn) in-pos
+                (js2-array-comp-loop-node-each-pos pn) each-pos
+                (js2-array-comp-loop-node-foreach-p pn) foreach-p
+                (js2-array-comp-loop-node-lp pn) lp
+                (js2-array-comp-loop-node-rp pn) rp)
+          (js2-node-add-children pn iter obj))
+      (js2-pop-scope))
+    pn))
+
+(defun js2-parse-object-literal ()
+  (let ((pos js2-token-beg)
+        tt
+        elems
+        result
+        after-comma
+        (continue t))
+    (while continue
+      (setq tt (js2-peek-token))
+      (cond
+       ;; {foo: ...}, {'foo': ...}, {get foo() {...}}, or {set foo(x) {...}}
+       ((or (js2-valid-prop-name-token tt)
+            (= tt js2-STRING))
+        (setq after-comma nil
+              result (js2-parse-named-prop tt))
+        (if (and (null result)
+                 (not js2-recover-from-parse-errors))
+            (setq continue nil)
+          (push result elems)))
+       ;; {12: x} or {10.7: x}
+       ((= tt js2-NUMBER)
+        (js2-consume-token)
+        (setq after-comma nil)
+        (push (js2-parse-plain-property (make-js2-number-node)) elems))
+       ;; trailing comma
+       ((= tt js2-RC)
+        (setq continue nil)
+        (if after-comma
+            (js2-parse-warn-trailing-comma &quot;msg.extra.trailing.comma&quot;
+                                           pos elems after-comma)))
+       (t
+        (js2-report-error &quot;msg.bad.prop&quot;)
+        (unless js2-recover-from-parse-errors
+          (setq continue nil))))         ; end switch
+      (if (js2-match-token js2-COMMA)
+          (setq after-comma js2-token-end)
+        (setq continue nil)))           ; end loop
+    (js2-must-match js2-RC &quot;msg.no.brace.prop&quot;)
+    (setq result (make-js2-object-node :pos pos
+                                       :len (- js2-ts-cursor pos)
+                                       :elems (nreverse elems)))
+    (apply #'js2-node-add-children result (js2-object-node-elems result))
+    result))
+
+(defun js2-parse-named-prop (tt)
+  &quot;Parse a name, string, or getter/setter object property.&quot;
+  (js2-consume-token)
+  (let ((string-prop (and (= tt js2-STRING)
+                          (make-js2-string-node)))
+        expr
+        (ppos js2-token-beg)
+        (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 &quot;get&quot;)
+                 (string= prop &quot;set&quot;)))
+        (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 &amp; use peeked name
+          (js2-parse-getter-setter-prop ppos name (string= prop &quot;get&quot;)))
+      ;; regular prop
+      (prog1
+          (setq expr (js2-parse-plain-property (or string-prop name)))
+        (js2-set-face ppos pend
+                      (if (js2-function-node-p
+                           (js2-object-prop-node-right expr))
+                          'font-lock-function-name-face
+                        'font-lock-variable-name-face)
+                      'record)))))
+
+(defun js2-parse-plain-property (prop)
+  &quot;Parse a non-getter/setter property in an object literal.
+PROP is the node representing the property:  a number, name or string.&quot;
+  (js2-must-match js2-COLON &quot;msg.no.colon.prop&quot;)
+  (let* ((pos (js2-node-pos prop))
+        (colon (- js2-token-beg pos))
+        (expr (js2-parse-assign-expr))
+        (result (make-js2-object-prop-node
+                 :pos pos
+                 ;; don't include last consumed token in length
+                 :len (- (+ (js2-node-pos expr)
+                            (js2-node-len expr))
+                         pos)
+                 :left prop
+                 :right expr
+                 :op-pos colon)))
+    (js2-node-add-children result prop expr)
+    result))
+
+(defun js2-parse-getter-setter-prop (pos prop get-p)
+  &quot;Parse getter or setter property in an object literal.
+JavaScript syntax is:
+
+  { get foo() {...}, set foo(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'.&quot;
+  (let ((type (if get-p js2-GET js2-SET))
+        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)
+        (js2-report-error &quot;msg.bad.prop&quot;)
+      (if (plusp (length (js2-function-name fn)))
+          (js2-report-error &quot;msg.bad.prop&quot;)))
+    (js2-node-set-prop fn 'GETTER_SETTER type)  ; for codegen
+    (setq end (js2-node-end fn)
+          result (make-js2-getter-setter-node :type type
+                                              :pos pos
+                                              :len (- end pos)
+                                              :left prop
+                                              :right fn))
+    (js2-node-add-children result prop fn)
+    result))
+
+(defun js2-create-name-node (&amp;optional check-activation-p token)
+  &quot;Create a name node using the token info from last scanned name.
+In some cases we need to either synthesize a name node, or we lost
+the name token information by peeking.  If the TOKEN parameter is
+not `js2-NAME', then we use the token info saved in instance vars.&quot;
+  (let ((beg js2-token-beg)
+        (s js2-ts-string)
+        name)
+    (when (/= js2-current-token js2-NAME)
+      (setq beg (or js2-prev-name-token-start js2-ts-cursor)
+            s js2-prev-name-token-string
+            js2-prev-name-token-start nil
+            js2-prev-name-token-string nil))
+    (setq name (make-js2-name-node :pos beg
+                                   :name s
+                                   :len (length s)))
+    (if check-activation-p
+        (js2-check-activation-name s (or token js2-NAME)))
+    name))
+
+;;; Indentation support
+
+;; This indenter is based on Karl Landstr&#246;m's &quot;javascript.el&quot; indenter.
+;; Karl cleverly deduces that the desired indentation level is often a
+;; function of paren/bracket/brace nesting depth, which can be determined
+;; quickly via the built-in `parse-partial-sexp' function.  His indenter
+;; then does some equally clever checks to see if we're in the context of a
+;; substatement of a possibly braceless statement keyword such as if, while,
+;; or finally.  This approach yields pretty good results.
+
+;; The indenter is often &quot;wrong&quot;, however, and needs to be overridden.
+;; The right long-term solution is probably to emulate (or integrate
+;; with) cc-engine, but it's a nontrivial amount of coding.  Even when a
+;; parse tree from `js2-parse' is present, which is not true at the
+;; moment the user is typing, computing indentation is still thousands
+;; of lines of code to handle every possible syntactic edge case.
+
+;; In the meantime, the compromise solution is that we offer a &quot;bounce
+;; indenter&quot;, configured with `js2-bounce-indent-p', which cycles the
+;; current line indent among various likely guess points.  This approach
+;; is far from perfect, but should at least make it slightly easier to
+;; move the line towards its desired indentation when manually
+;; overriding Karl's heuristic nesting guesser.
+
+;; I've made miscellaneous tweaks to Karl's code to handle some Ecma
+;; extensions such as `let' and Array comprehensions.  Major kudos to
+;; 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
+   '(&quot;catch&quot; &quot;do&quot; &quot;else&quot; &quot;finally&quot; &quot;for&quot; &quot;if&quot; &quot;try&quot; &quot;while&quot; &quot;with&quot; &quot;let&quot;)
+   'words)
+  &quot;Regular expression matching keywords that are optionally
+followed by an opening brace.&quot;)
+
+(defconst js-indent-operator-re
+  (concat &quot;[-+*/%&lt;&gt;=&amp;^|?:.]\\([^-+*/]\\|$\\)\\|&quot;
+          (regexp-opt '(&quot;in&quot; &quot;instanceof&quot;) 'words))
+  &quot;Regular expression matching operators that affect indentation
+of continued expressions.&quot;)
+
+;; This function has horrible results if you're typing an array
+;; such as [[1, 2], [3, 4], [5, 6]].  Bounce indenting -really- sucks
+;; in conjunction with electric-indent, so just disabling it.
+(defsubst js2-code-at-bol-p ()
+  &quot;Return t if the first character on line is non-whitespace.&quot;
+  nil)
+
+(defun js2-insert-and-indent (key)
+  &quot;Run command bound to key and indent current line. Runs the command
+bound to KEY in the global keymap and indents the current line.&quot;
+  (interactive (list (this-command-keys)))
+  (let ((cmd (lookup-key (current-global-map) key)))
+    (if (commandp cmd)
+        (call-interactively cmd)))
+  ;; don't do the electric keys inside comments or strings,
+  ;; and don't do bounce-indent with them.
+  (let ((parse-state (parse-partial-sexp (point-min) (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 &amp;optional bound count)
+  &quot;Auxiliary function for `js-re-search-forward'.&quot;
+  (let ((parse)
+        (saved-point (point-min)))
+    (while (&gt; count 0)
+      (re-search-forward regexp bound)
+      (setq parse (parse-partial-sexp saved-point (point)))
+      (cond ((nth 3 parse)
+             (re-search-forward
+              (concat &quot;\\([^\\]\\|^\\)&quot; (string (nth 3 parse)))
+              (save-excursion (end-of-line) (point)) t))
+            ((nth 7 parse)
+             (forward-line))
+            ((or (nth 4 parse)
+                 (and (eq (char-before) ?\/) (eq (char-after) ?\*)))
+             (re-search-forward &quot;\\*/&quot;))
+            (t
+             (setq count (1- count))))
+      (setq saved-point (point))))
+  (point))
+
+(defun js-re-search-forward (regexp &amp;optional bound noerror count)
+  &quot;Search forward but ignore strings and comments. Invokes
+`re-search-forward' but treats the buffer as if strings and
+comments have been removed.&quot;
+  (let ((saved-point (point))
+        (search-expr
+         (cond ((null count)
+                '(js-re-search-forward-inner regexp bound 1))
+               ((&lt; count 0)
+                '(js-re-search-backward-inner regexp bound (- count)))
+               ((&gt; count 0)
+                '(js-re-search-forward-inner regexp bound count)))))
+    (condition-case err
+        (eval search-expr)
+      (search-failed
+       (goto-char saved-point)
+       (unless noerror
+         (error (error-message-string err)))))))
+
+(defun js-re-search-backward-inner (regexp &amp;optional bound count)
+  &quot;Auxiliary function for `js-re-search-backward'.&quot;
+  (let ((parse)
+        (saved-point (point-min)))
+    (while (&gt; count 0)
+      (re-search-backward regexp bound)
+      (setq parse (parse-partial-sexp saved-point (point)))
+      (cond ((nth 3 parse)
+             (re-search-backward
+              (concat &quot;\\([^\\]\\|^\\)&quot; (string (nth 3 parse)))
+              (save-excursion (beginning-of-line) (point)) t))
+            ((nth 7 parse)
+             (goto-char (nth 8 parse)))
+            ((or (nth 4 parse)
+                 (and (eq (char-before) ?/) (eq (char-after) ?*)))
+             (re-search-backward &quot;/\\*&quot;))
+            (t
+             (setq count (1- count))))))
+  (point))
+
+(defun js-re-search-backward (regexp &amp;optional bound noerror count)
+  &quot;Search backward but ignore strings and comments. Invokes
+`re-search-backward' but treats the buffer as if strings and
+comments have been removed.&quot;
+  (let ((saved-point (point))
+        (search-expr
+         (cond ((null count)
+                '(js-re-search-backward-inner regexp bound 1))
+               ((&lt; count 0)
+                '(js-re-search-forward-inner regexp bound (- count)))
+               ((&gt; count 0)
+                '(js-re-search-backward-inner regexp bound count)))))
+    (condition-case err
+        (eval search-expr)
+      (search-failed
+       (goto-char saved-point)
+       (unless noerror
+         (error (error-message-string err)))))))
+
+(defun js-looking-at-operator-p ()
+  &quot;Return non-nil if text after point is an operator (that is not
+a comma).&quot;
+  (save-match-data
+    (and (looking-at js-indent-operator-re)
+         (or (not (looking-at &quot;:&quot;))
+             (save-excursion
+               (and (js-re-search-backward &quot;[?:{]\\|\\&lt;case\\&gt;&quot; nil t)
+                    (looking-at &quot;?&quot;)))))))
+
+(defun js-continued-expression-p ()
+  &quot;Returns non-nil if the current line continues an expression.&quot;
+  (save-excursion
+    (back-to-indentation)
+    (or (js-looking-at-operator-p)
+        (and (js-re-search-backward &quot;\n&quot; nil t)
+	     (progn
+	       (skip-chars-backward &quot; \t&quot;)
+	       (backward-char)
+	       (and (js-looking-at-operator-p)
+		    (and (progn (backward-char)
+				(not (looking-at &quot;\\*\\|++\\|--\\|/[/*]&quot;))))))))))
+
+(defun js-end-of-do-while-loop-p ()
+  &quot;Returns non-nil if word after point is `while' of a do-while
+statement, else returns nil. A braceless do-while statement
+spanning several lines requires that the start of the loop is
+indented to the same column as the current line.&quot;
+  (interactive)
+  (save-excursion
+    (save-match-data
+      (when (looking-at &quot;\\s-*\\&lt;while\\&gt;&quot;)
+	(if (save-excursion
+	      (skip-chars-backward &quot;[ \t\n]*}&quot;)
+	      (looking-at &quot;[ \t\n]*}&quot;))
+	    (save-excursion
+	      (backward-list) (backward-word 1) (looking-at &quot;\\&lt;do\\&gt;&quot;))
+	  (js-re-search-backward &quot;\\&lt;do\\&gt;&quot; (point-at-bol) t)
+	  (or (looking-at &quot;\\&lt;do\\&gt;&quot;)
+	      (let ((saved-indent (current-indentation)))
+		(while (and (js-re-search-backward &quot;^[ \t]*\\&lt;&quot; nil t)
+			    (/= (current-indentation) saved-indent)))
+		(and (looking-at &quot;[ \t]*\\&lt;do\\&gt;&quot;)
+		     (not (js-re-search-forward
+			   &quot;\\&lt;while\\&gt;&quot; (point-at-eol) t))
+		     (= (current-indentation) saved-indent)))))))))
+
+(defun js-ctrl-statement-indentation ()
+  &quot;Returns the proper indentation of the current line if it
+starts the body of a control statement without braces, else
+returns nil.&quot;
+  (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 &quot;{&quot;))
+                   (js-re-search-backward &quot;[[:graph:]]&quot; nil t)
+                   (not (looking-at &quot;[{([]&quot;))
+                   (progn
+                     (forward-char)
+                     ;; scan-sexps sometimes throws an error
+                     (ignore-errors (backward-sexp))
+                     (when (looking-at &quot;(&quot;) (backward-word 1))
+                     (and (save-excursion
+                            (skip-chars-backward &quot; \t}&quot; (point-at-bol))
+                            (bolp))
+                          (looking-at js-possibly-braceless-keyword-re)
+                          (not (js-end-of-do-while-loop-p))))))
+        (save-excursion
+          (goto-char (match-beginning 0))
+          (+ (current-indentation) js2-basic-offset))))))
+
+(defun js2-indent-in-array-comp (parse-status)
+  &quot;Return non-nil if we think we're in an array comprehension.
+In particular, return the buffer position of the first `for' kwd.&quot;
+  (let ((end (point)))
+    (when (nth 1 parse-status)
+      (save-excursion
+        (goto-char (nth 1 parse-status))
+        (when (looking-at &quot;\\[&quot;)
+          (forward-char 1)
+          (js2-forward-sws)
+          (if (looking-at &quot;[[{]&quot;)
+              (let (forward-sexp-function) ; use lisp version
+                (forward-sexp)             ; skip destructuring form
+                (js2-forward-sws)
+                (if (and (/= (char-after) ?,) ; regular array
+                         (looking-at &quot;for&quot;))
+                    (match-beginning 0)))
+            ;; to skip arbitrary expressions we need the parser,
+            ;; so we'll just guess at it.
+            (if (re-search-forward &quot;[^,]* \\(for\\) &quot; end t)
+                (match-beginning 1))))))))
+
+(defun js2-array-comp-indentation (parse-status for-kwd)
+  (if (js2-same-line for-kwd)
+      ;; first continuation line
+      (save-excursion
+        (goto-char (nth 1 parse-status))
+        (forward-char 1)
+        (skip-chars-forward &quot; \t&quot;)
+        (current-column))
+    (save-excursion
+      (goto-char for-kwd)
+      (current-column))))
+
+(defun js-proper-indentation (parse-status)
+  &quot;Return the proper indentation for the current line.&quot;
+  (save-excursion
+    (back-to-indentation)
+    (let ((ctrl-stmt-indent (js-ctrl-statement-indentation))
+          (same-indent-p (looking-at &quot;[]})]\\|\\&lt;case\\&gt;\\|\\&lt;default\\&gt;&quot;))
+          (continued-expr-p (js-continued-expression-p))
+          (bracket (nth 1 parse-status))
+          beg)
+      (cond
+       ;; indent array comprehension continuation lines specially
+       ((and bracket
+             (not (js2-same-line bracket))
+             (setq beg (js2-indent-in-array-comp parse-status))
+             (&gt;= (point) (save-excursion
+                           (goto-char beg)
+                           (point-at-bol)))) ; at or after first loop?
+        (js2-array-comp-indentation parse-status beg))
+
+       (ctrl-stmt-indent)
+
+       (bracket
+        (goto-char bracket)
+        (cond
+         ((looking-at &quot;[({[][ \t]*\\(/[/*]\\|$\\)&quot;)
+          (let ((p (parse-partial-sexp (point-at-bol) (point))))
+            (when (save-excursion (skip-chars-backward &quot; \t)&quot;)
+                                  (looking-at &quot;)&quot;))
+              (backward-list))
+            (if (nth 1 p)
+                (progn (goto-char (1+ (nth 1 p)))
+                       (skip-chars-forward &quot; \t&quot;))
+              (back-to-indentation))
+            (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)
+            (skip-chars-forward &quot; \t&quot;))
+          (current-column))))
+
+       (continued-expr-p js2-basic-offset)
+       (t 0)))))
+
+(defun js2-lineup-comment (parse-status)
+  &quot;Indent a multi-line block comment continuation line.&quot;
+  (let* ((beg (nth 8 parse-status))
+         (first-line (js2-same-line beg))
+         (offset (save-excursion
+                   (goto-char beg)
+                   (if (looking-at &quot;/\\*&quot;)
+                       (+ 1 (current-column))
+                     0))))
+    (unless first-line
+      (indent-line-to offset))))
+
+(defun js2-backward-sws ()
+  &quot;Move backward through whitespace and comments.&quot;
+  (interactive)
+  (while (forward-comment -1)))
+
+(defun js2-forward-sws ()
+  &quot;Move forward through whitespace and comments.&quot;
+  (interactive)
+  (while (forward-comment 1)))
+
+(defsubst js2-current-indent (&amp;optional pos)
+  &quot;Return column of indentation on current line.
+If POS is non-nil, go to that point and return indentation for that line.&quot;
+  (save-excursion
+    (if pos
+        (goto-char pos))
+    (back-to-indentation)
+    (current-column)))
+
+(defsubst js2-arglist-close ()
+  &quot;Return non-nil if we're on a line beginning with a close-paren/brace.&quot;
+  (save-match-data
+    (save-excursion
+      (goto-char (point-at-bol))
+      (js2-forward-sws)
+      (looking-at &quot;[])}]&quot;))))
+
+(defsubst js2-indent-looks-like-label-p ()
+  (goto-char (point-at-bol))
+  (js2-forward-sws)
+  (looking-at (concat js2-mode-identifier-re &quot;:&quot;)))
+
+(defun js2-indent-in-objlit-p (parse-status)
+  &quot;Return non-nil if this looks like an object-literal entry.&quot;
+  (let ((start (nth 1 parse-status)))
+    (and
+     start
+     (save-excursion
+       (and (zerop (forward-line -1))
+            (not (&lt; (point) start))     ; crossed a {} boundary
+            (js2-indent-looks-like-label-p)))
+     (save-excursion
+       (js2-indent-looks-like-label-p)))))
+
+;; 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)
+  (save-excursion
+    (back-to-indentation)
+    (js2-backward-sws)
+    (and (eq (1- (point)) (nth 1 parse-status))
+         (eq (char-before) ?{)
+         (progn
+           (forward-char -1)
+           (skip-chars-backward &quot; \t&quot;)
+           (eq (char-before) ?\()))))
+
+(defsubst js2-indent-case-block-p ()
+  (save-excursion
+    (back-to-indentation)
+    (js2-backward-sws)
+    (goto-char (point-at-bol))
+    (skip-chars-forward &quot; \t&quot;)
+    (save-match-data
+      (looking-at &quot;case\\s-.+:&quot;))))
+
+(defsubst js2-syntax-bol ()
+  &quot;Return the point at the first non-whitespace char on the line.
+Returns `point-at-bol' if the line is empty.&quot;
+  (save-excursion
+    (beginning-of-line)
+    (skip-chars-forward &quot; \t&quot;)
+    (point)))
+
+(defun js2-bounce-indent (normal-col parse-status)
+  &quot;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.&quot;
+  (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)
+    ;; temporarily don't record undo info, if user requested this
+    (if js2-mode-indent-inhibit-undo
+        (setq buffer-undo-list t))
+    (unwind-protect
+        (progn
+          ;; first likely point:  indent from beginning of previous code line
+          (push (setq basic-offset
+                      (+ (save-excursion
+                           (back-to-indentation)
+                           (js2-backward-sws)
+                           (back-to-indentation)
+                           (setq prev-line-col (current-column)))
+                         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.
+          (push (setq basic-offset
+                      (+ (save-excursion
+                           (back-to-indentation)
+                           (js2-backward-sws)
+                           (back-to-indentation)
+                           (setq prev-line-col (current-column)))
+                         (* 2 js2-basic-offset)))
+                positions)
+
+          ;; second likely point:  indent from assign-expr RHS.  This
+          ;; is just a crude guess based on finding &quot; = &quot; on the previous
+          ;; line containing actual code.
+          (setq pos (save-excursion
+                      (save-match-data
+                        (forward-line -1)
+                        (goto-char (point-at-bol))
+                        (when (re-search-forward &quot;\\s-+\\(=\\)\\s-+&quot;
+                                                 (point-at-eol) t)
+                          (goto-char (match-end 1))
+                          (skip-chars-forward &quot; \t\r\n&quot;)
+                          (current-column)))))
+          (when pos
+            (incf pos js2-basic-offset)
+            (unless (member pos positions)
+              (push pos positions)))
+
+          ;; 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
+          ;; elements.
+          (setq pos (save-excursion
+                      (goto-char (point-at-bol))
+                      (js2-backward-sws)
+                      (back-to-indentation)
+                      (prog1
+                          (current-column)
+                        ;; while we're here, look for trailing comma
+                        (if (save-excursion
+                              (goto-char (point-at-eol))
+                              (js2-backward-sws)
+                              (eq (char-before) ?,))
+                            (setq arglist-cont (1- (point)))))))
+          (when pos
+            (if (and (or arglist-cont
+                         (js2-indent-in-objlit-p parse-status))
+                     (not (js2-arglist-close)))
+                (setq same-indent pos))
+            (unless (member pos positions)
+              (push pos positions)))
+
+          ;; fourth likely point:  first preceding code with less indentation
+          ;; than the immediately preceding code line.
+          (setq pos (save-excursion
+                      (js2-backward-sws)
+                      (back-to-indentation)
+                      (setq anchor (current-column))
+                      (while (and (zerop (forward-line -1))
+                                  (&gt;= (progn
+                                        (back-to-indentation)
+                                        (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 '&lt;)))
+          (setq positions (cons normal-col (delete normal-col positions)))
+
+          ;; comma-list continuation lines:  prev line indent takes precedence
+          (if same-indent
+              (setq positions
+                    (cons same-indent
+                          (sort (delete same-indent positions) '&lt;))))
+
+          ;; 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))))
+
+          ;; 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).
+           ((and js2-mode-indent-ignore-first-tab
+                 pos
+                 ;; first time pressing TAB on this line?
+                 (not (eq js2-mode-last-indented-line current-line)))
+            ;; do nothing
+            (setq computed-pos nil))
+           ;; case 1:  only one computed position =&gt; use it
+           ((null (cdr positions))
+            (setq computed-pos 0))
+           ;; case 2:  not on any of the computed spots =&gt; use main spot
+           ((not pos)
+            (setq computed-pos 0))
+           ;; case 3:  on last position:  cycle to first position
+           ((null (cdr pos))
+            (setq computed-pos 0))
+           ;; case 4:  on intermediate position:  cycle to next position
+           (t
+            (setq computed-pos (js2-position (second pos) positions))))
+
+          ;; see if any hooks want to indent; otherwise we do it
+          (loop with result = nil
+                for hook in js2-indent-hook
+                while (null result)
+                do
+                (setq result (funcall hook positions computed-pos))
+                finally do
+                (unless (or result (null computed-pos))
+                  (indent-line-to (nth computed-pos positions)))))
+
+      ;; finally
+      (if js2-mode-indent-inhibit-undo
+          (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 ()
+  &quot;Return t if we're in a 1-line comment continuation.
+If so, we don't ever want to use bounce-indent.&quot;
+  (save-excursion
+    (save-match-data
+      (and (progn
+             (forward-line 0)
+             (looking-at &quot;\\s-*//&quot;))
+           (progn
+             (forward-line -1)
+             (forward-line 0)
+             (when (looking-at &quot;\\s-*$&quot;)
+               (js2-backward-sws)
+               (forward-line 0))
+             (looking-at &quot;\\s-*//&quot;))))))
+
+(defun js2-indent-line ()
+  &quot;Indent the current line as JavaScript source text.&quot;
+  (interactive)
+  (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
+                          (parse-partial-sexp (point-min)
+                                              (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)))))))
+
+(defun js2-indent-region (start end)
+  &quot;Indent the region, but don't use bounce indenting.&quot;
+  (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 '(&quot;\\.js$&quot; . js2-mode))
 
@@ -116,7 +10160,6 @@
 (defun js2-mode ()
   &quot;Major mode for editing JavaScript code.&quot;
   (interactive)
-  (js2-mode-check-compat)
   (kill-all-local-variables)
   (set-syntax-table js2-mode-syntax-table)
   (use-local-map js2-mode-map)
@@ -129,12 +10172,18 @@
        (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)
-  (set (make-local-variable 'fill-paragraph-function) #'js2-fill-paragraph)
+
+  ;; I tried an &quot;improvement&quot; 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 '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
@@ -145,14 +10194,22 @@
   ;; 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
+        c-comment-start-regexp &quot;/[*/]\\|\\s|&quot;
         c-paragraph-start js2-paragraph-start
         c-paragraph-separate &quot;$&quot;
         comment-start-skip js2-comment-start-skip
         c-syntactic-ws-start js2-syntactic-ws-start
         c-syntactic-ws-end js2-syntactic-ws-end
         c-syntactic-eol js2-syntactic-eol)
-  (if js2-emacs22
-      (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
@@ -190,18 +10247,8 @@
   (js2-reparse)
   (run-hooks 'js2-mode-hook))
 
-(defun js2-mode-check-compat ()
-  &quot;Signal an error if we can't run with this version of Emacs.&quot;
-  (if (and js2-mode-must-byte-compile
-           (not (byte-code-function-p (symbol-function 'js2-mode))))
-      (error &quot;You must byte-compile js2-mode before using it.&quot;))
-  (if (and (boundp 'running-xemacs)
-           running-xemacs)
-      (error &quot;js2-mode is not compatible with XEmacs&quot;))
-  (unless (&gt;= emacs-major-version 21)
-    (error &quot;js2-mode requires GNU Emacs version 21 or higher&quot;)))
-
 (defun js2-mode-exit ()
+  &quot;Exit `js2-mode' and clean up.&quot;
   (interactive)
   (when js2-mode-node-overlay
     (delete-overlay js2-mode-node-overlay)
@@ -223,8 +10270,9 @@ You can disable this by customizing `js2-cleanup-whitespace'.&quot;
       ;; don't change trailing whitespace on current line
       (unless (eq (current-column) col)
         (indent-to col)))))
-      
+
 (defsubst js2-mode-reset-timer ()
+  &quot;Cancel any existing parse timer and schedule a new one.&quot;
   (if js2-mode-parse-timer
       (cancel-timer js2-mode-parse-timer))
   (setq js2-mode-parsing nil)
@@ -232,7 +10280,17 @@ You can disable this by customizing `js2-cleanup-whitespace'.&quot;
         (run-with-idle-timer js2-idle-timer-delay nil #'js2-reparse)))
 
 (defun js2-mode-edit (beg end len)
-  &quot;Schedule a new parse after buffer is edited.&quot;
+  &quot;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.&quot;
+  (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 (&gt; 0 len)
+                  (/= (line-number-at-pos end) line)))
+         (js2-mode-mundanify-parens)))
   (setq js2-mode-buffer-dirty-p t)
   (js2-mode-hide-overlay)
   (js2-mode-reset-timer))
@@ -248,11 +10306,11 @@ Hopefully the Emacs maintainers can help figure out a way to make it work.&quot;
              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 &quot;look, fontify the goddamn buffer
+    ;; provide any option for saying &quot;look, fontify the farging buffer
     ;; with just the keywords already&quot;.  Argh.
     (setq font-lock-defaults (list font-lock-keywords 'keywords-only))
     (let (font-lock-verbose)
-      (font-lock-default-fontify-buffer))))
+      (font-lock-fontify-buffer))))
 
 (defun js2-reparse (&amp;optional force)
   &quot;Re-parse current buffer after user finishes some data entry.
@@ -268,9 +10326,11 @@ buffer will only rebuild its `js2-mode-ast' if the buffer is dirty.&quot;
           (when (or js2-mode-buffer-dirty-p force)
             (js2-remove-overlays)
             (js2-with-unmodifying-text-property-changes
+              (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-mode-deferred-properties nil
+                    js2-additional-externs nil)
               (if js2-mode-verbose-parse-p
                   (message &quot;parsing...&quot;))
               (setq time
@@ -278,11 +10338,13 @@ buffer will only rebuild its `js2-mode-ast' if the buffer is dirty.&quot;
                      (setq interrupted-p
                            (catch 'interrupted
                              (setq js2-mode-ast (js2-parse))
-                             (js2-mode-fontify-regions)
+                             (when (plusp js2-highlight-level)
+                               (js2-mode-fontify-regions))
                              (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 (&gt;= js2-highlight-level 1)
                                  (js2-highlight-jsdoc js2-mode-ast))
                              nil))))
@@ -293,7 +10355,6 @@ buffer will only rebuild its `js2-mode-ast' if the buffer is dirty.&quot;
                     (js2-mode-reset-timer))
                 (if js2-mode-verbose-parse-p
                     (message &quot;Parse time: %s&quot; time)))))
-        ;; finally
         (setq js2-mode-parsing nil)
         (unless interrupted-p
           (setq js2-mode-parse-timer nil))))))
@@ -322,7 +10383,8 @@ buffer will only rebuild its `js2-mode-ast' if the buffer is dirty.&quot;
                    &quot;nil&quot;))))))
 
 (defun js2-mode-hide-overlay (&amp;optional p1 p2)
-  &quot;Remove the debugging overlay when the point moves.&quot;
+  &quot;Remove the debugging overlay when the point moves.
+P1 and P2 are the old and new values of point, respectively.&quot;
   (when js2-mode-node-overlay
     (let ((beg (overlay-start js2-mode-node-overlay))
           (end (overlay-end js2-mode-node-overlay)))
@@ -336,7 +10398,7 @@ buffer will only rebuild its `js2-mode-ast' if the buffer is dirty.&quot;
         (setq js2-mode-node-overlay nil)))))
 
 (defun js2-mode-reset ()
-  &quot;Debugging helper; resets everything.&quot;
+  &quot;Debugging helper:  reset everything.&quot;
   (interactive)
   (js2-mode-exit)
   (js2-mode))
@@ -353,19 +10415,26 @@ E is a list of ((MSG-KEY MSG-ARG) BEG END).&quot;
          (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 'js2 t)
+    (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)))
 
 (defun js2-remove-overlays ()
-  &quot;Remove overlays from buffer that have a `js2' property.&quot;
+  &quot;Remove overlays from buffer that have a `js2-error' property.&quot;
   (let ((beg (point-min))
         (end (point-max)))
     (save-excursion
       (dolist (o (overlays-in beg end))
-        (when (overlay-get o 'js2)
+        (when (overlay-get o 'js2-error)
           (delete-overlay o))))))
 
+(defun js2-error-at-point (&amp;optional pos)
+  &quot;Return non-nil if there's an error overlay at POS.
+Defaults to point.&quot;
+  (loop with pos = (or pos (point))
+        for o in (overlays-at pos)
+        thereis (overlay-get o 'js2-error)))
+
 (defun js2-mode-fontify-regions ()
   &quot;Apply fontifications recorded during parsing.&quot;
   ;; We defer clearing faces as long as possible to eliminate flashing.
@@ -442,15 +10511,16 @@ This ensures that the counts and `next-error' are correct.&quot;
      (t
       ;; should probably figure out what the mode-map says we should do
       (if js2-indent-on-enter-key
-          (let ((js2-bounce-indent-flag nil))
+          (let ((js2-bounce-indent-p nil))
             (js2-indent-line)))
       (insert &quot;\n&quot;)
       (if js2-enter-indents-newline
-          (let ((js2-bounce-indent-flag nil))
+          (let ((js2-bounce-indent-p nil))
             (js2-indent-line)))))))
 
 (defun js2-mode-split-string (parse-status)
-  &quot;Turn a newline in mid-string into a string concatenation.&quot;
+  &quot;Turn a newline in mid-string into a string concatenation.
+PARSE-STATUS is as documented in `parse-partial-sexp'.&quot;
   (let* ((col (current-column))
          (quote-char (nth 3 parse-status))
          (quote-string (string quote-char))
@@ -504,7 +10574,7 @@ This ensures that the counts and `next-error' are correct.&quot;
            (and first-line
                 (eolp)
                 (save-excursion
-                  (skip-syntax-forward &quot; &quot;)
+                  (skip-chars-forward &quot; \t\r\n&quot;)
                   (not (eq (char-after) ?*))))))
     (insert &quot;\n&quot;)
     (cond
@@ -523,95 +10593,6 @@ This ensures that the counts and `next-error' are correct.&quot;
         (indent-to col)
         (insert &quot;// &quot;))))))
 
-(defun js2-fill-string (beg quote)
-  &quot;Line-wrap a single-line string into a multi-line string.
-BEG is the string beginning, QUOTE is the quote char.&quot;
-  (let* ((squote (string quote))
-         (end (if (re-search-forward (concat &quot;[^\\]&quot; squote)
-                                     (point-at-eol) t)
-                  (1+ (match-beginning 0))
-                (point-at-eol)))
-         (tag (make-marker))
-         (fill-column (- fill-column 4)))  ; make room
-    (unwind-protect
-        (progn
-          (move-marker tag end)
-          (fill-paragraph nil)
-          (goto-char beg)
-          (while (not (js2-same-line tag))
-            (goto-char (point-at-eol))
-            (insert squote)
-            (when (zerop (forward-line 1))
-              (back-to-indentation)
-              (if (looking-at (concat squote &quot;\\s-*$&quot;))
-                  (progn
-                    (setq end (point-at-eol))
-                    (forward-line -1)
-                    (delete-region (point-at-eol) end))
-                (insert &quot;+ &quot; squote)))))
-      (move-marker tag nil))))
-
-(defun js2-fill-paragraph (arg)
-  &quot;Fill paragraph after point.  Prefix ARG means justify as well.
-Has special handling for filling in comments and strings.&quot;
-  (let* ((parse-status (save-excursion
-                         (parse-partial-sexp (point-min) (point))))
-         (quote-char (or (nth 3 parse-status)
-                         (save-match-data
-                           (if (looking-at &quot;[\&quot;\']&quot;)
-                               (char-after))))))
-    (cond
-     (quote-char
-      (js2-fill-string (or (nth 8 parse-status)
-                           (point))
-                       quote-char)
-      t) ; or fill-paragraph does evil things afterwards
-     ((nth 4 parse-status)  ; in block comment?
-      (js2-fill-comment parse-status arg))
-     (t
-      (fill-paragraph arg)))))
-
-(defun js2-fill-comment (parse-status arg)
-  &quot;Fill-paragraph in a block comment.&quot;
-  (let* ((beg (nth 8 parse-status))
-         (end (save-excursion
-                (goto-char beg)
-                (re-search-forward &quot;[^\\]\\*/&quot; nil t)))
-         indent
-         end-marker)
-    (when end
-      (setq end-marker (make-marker))
-      (move-marker end-marker end))
-    (when (and end js2-mode-squeeze-spaces)
-      (save-excursion
-        (save-restriction
-          (narrow-to-region beg end)
-          (goto-char (point-min))
-          (while (re-search-forward &quot;[ \t][ \t]+&quot; nil t)
-            (replace-match &quot; &quot;)))))
-    ;; `c-fill-paragraph' doesn't indent the continuation stars properly
-    ;; if the comment isn't left-justified.  They align to the first star
-    ;; on the first continuation line after the comment-open, so we make
-    ;; sure the first continuation line has the proper indentation.
-    (save-excursion
-      (goto-char beg)
-      (setq indent (1+ (current-column)))
-      (goto-char (point-at-eol))
-      (skip-chars-forward &quot; \t\r\n&quot;)
-      (indent-line-to indent)
-
-      ;; Invoke `c-fill-paragraph' from the first continuation line,
-      ;; since it provides better results.  Otherwise if you're on the
-      ;; last line, it doesn't prefix with stars the way you'd expect.
-      ;; TODO:  write our own fill function that works in Emacs 21
-      (c-fill-paragraph arg))
-
-    ;; last line is typically indented wrong, so fix it
-    (when end-marker
-      (save-excursion
-        (goto-char end-marker)
-        (js2-indent-line)))))
-
 (defun js2-beginning-of-line ()
   &quot;Toggles point between bol and first non-whitespace char in line.
 Also moves past comment delimiters when inside comments.&quot;
@@ -680,58 +10661,119 @@ Actually returns the quote character that begins the string.&quot;
      (or (nth 3 parse-state)
          (nth 4 parse-state)))))
 
-(defun js2-mode-match-curly (arg)
-  &quot;Insert matching curly-brace.&quot;
-  (interactive &quot;p&quot;)
-  (insert &quot;{&quot;)
-  (if current-prefix-arg
-      (save-excursion
-        (insert &quot;}&quot;))
-    (unless (or (not (looking-at &quot;\\s-*$&quot;))
-                (js2-mode-inside-comment-or-string))
-      (undo-boundary)
-
-      ;; absolutely mystifying bug:  when inserting the next &quot;\n&quot;,
-      ;; 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 &quot;{&quot;, 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 &quot;{&quot;.
-      (insert &quot;\n&quot;)
-      (ignore-errors
-        (incf (cadr buffer-undo-list)))
+(defsubst js2-make-magic-delimiter (delim &amp;optional pos)
+  &quot;Add `js2-magic' and `js2-magic-paren-face' to DELIM, a string.
+Sets value of `js2-magic' text property to line number at POS.&quot;
+  (propertize delim
+              'js2-magic (line-number-at-pos pos)
+              'face 'js2-magic-paren-face))
+
+(defun js2-mode-match-delimiter (open close)
+  &quot;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.&quot;
+  (insert open)
+  (when (and (looking-at &quot;\\s-*\\([[:punct:]]\\|$\\)&quot;)
+             (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)))))
 
-      (js2-indent-line)
-      (save-excursion
-        (insert &quot;\n}&quot;)
-        (let ((js2-bounce-indent-flag (js2-code-at-bol-p)))
-          (js2-indent-line))))))
-    
 (defun js2-mode-match-bracket ()
   &quot;Insert matching bracket.&quot;
   (interactive)
-  (insert &quot;[&quot;)
-  (unless (or (not (looking-at &quot;\\s-*$&quot;))
-              (js2-mode-inside-comment-or-string))
-    (save-excursion
-      (insert &quot;]&quot;))
-    (when js2-auto-indent-flag
-      (let ((js2-bounce-indent-flag (js2-code-at-bol-p)))
-        (js2-indent-line)))))
+  (js2-mode-match-delimiter &quot;[&quot; &quot;]&quot;))
 
 (defun js2-mode-match-paren ()
   &quot;Insert matching paren unless already inserted.&quot;
   (interactive)
-  (insert &quot;(&quot;)
-  (unless (or (not (looking-at &quot;\\s-*$&quot;))
-              (js2-mode-inside-comment-or-string))
+  (js2-mode-match-delimiter &quot;(&quot; &quot;)&quot;))
+
+(defun js2-mode-match-curly (arg)
+  &quot;Insert matching curly-brace.
+With prefix arg, no formatting or indentation will occur -- the close-brace
+is simply inserted directly at the point.&quot;
+  (interactive &quot;p&quot;)
+  (let (try-pos)
+    (cond
+     (current-prefix-arg
+      (js2-mode-match-delimiter &quot;{&quot; &quot;}&quot;))
+     ((and js2-auto-insert-catch-block
+           (setq try-pos (if (looking-back &quot;\\s-*\\(try\\)\\s-*&quot;
+                                           (point-at-bol))
+                             (match-beginning 1))))
+      (js2-insert-catch-skel try-pos))
+     (t
+      ;; Otherwise try to do something smarter.
+      (insert &quot;{&quot;)
+      (unless (or (not (looking-at &quot;\\s-*$&quot;))
+                  (save-excursion
+                    (skip-chars-forward &quot; \t\r\n&quot;)
+                    (and (looking-at &quot;}&quot;)
+                         (js2-error-at-point)))
+                  (js2-mode-inside-comment-or-string))
+        (undo-boundary)
+        ;; absolutely mystifying bug:  when inserting the next &quot;\n&quot;,
+        ;; 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 &quot;{&quot;, 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 &quot;{&quot;.
+        (insert &quot;\n&quot;)
+        (ignore-errors
+          (incf (cadr buffer-undo-list)))
+        (js2-indent-line)
+        (save-excursion
+          (insert &quot;\n}&quot;)
+          (let ((js2-bounce-indent-p (js2-code-at-bol-p)))
+            (js2-indent-line))))))))
+
+(defun js2-insert-catch-skel (try-pos)
+  &quot;Complete a try/catch block after inserting a { following a try keyword.
+Rationale is that a try always needs a catch or a finally, and the catch is
+the more likely of the two.
+
+TRY-POS is the buffer position of the try keyword.  The open-curly should
+already have been inserted.&quot;
+  (insert &quot;{&quot;)
+  (let ((try-col (save-excursion
+                   (goto-char try-pos)
+                   (current-column))))
+    (insert &quot;\n&quot;)
+    (undo-boundary)
+    (js2-indent-line) ;; indent the blank line where cursor will end up
     (save-excursion
-      (insert &quot;)&quot;))
-    (when js2-auto-indent-flag
-      (let ((js2-bounce-indent-flag (js2-code-at-bol-p)))
-        (js2-indent-line)))))
+      (insert &quot;\n&quot;)
+      (indent-to try-col)
+      (insert &quot;} catch (x) {\n\n&quot;)
+      (indent-to try-col)
+      (insert &quot;}&quot;))))
+
+(defun js2-mode-highlight-magic-parens ()
+  &quot;Re-highlight magic parens after parsing nukes the 'face prop.&quot;
+  (let ((beg (point-min))
+        end)
+    (while (setq beg (next-single-property-change beg 'js2-magic))
+      (setq end (next-single-property-change (1+ beg) 'js2-magic))
+      (if (get-text-property beg 'js2-magic)
+          (js2-with-unmodifying-text-property-changes
+            (put-text-property beg (or end (1+ beg))
+                               'face 'js2-magic-paren-face))))))
+
+(defun js2-mode-mundanify-parens ()
+  &quot;Clear all magic parens and brackets.&quot;
+  (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)))
@@ -761,7 +10803,7 @@ Actually returns the quote character that begins the string.&quot;
                 (concat &quot;\\&quot; quote-string))))
      (t
       (insert quote-string)))))        ; else terminate the string
-      
+
 (defun js2-mode-match-single-quote ()
   &quot;Insert matching single-quote.&quot;
   (interactive)
@@ -776,26 +10818,34 @@ Actually returns the quote character that begins the string.&quot;
   (interactive)
   (js2-match-quote &quot;\&quot;&quot;))
 
+;; 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 ()
-  &quot;Skip over close-paren rather than inserting, where appropriate.
-Uses some heuristics to try to figure out the right thing to do.&quot;
+  &quot;Skip over close-paren rather than inserting, where appropriate.&quot;
   (interactive)
-  (let* ((parse-status (parse-partial-sexp (point-min) (point)))
+  (let* ((here (point))
+         (parse-status (parse-partial-sexp (point-min) here))
          (open-pos (nth 1 parse-status))
-         (close last-input-char)
+         (close last-input-event)
          (open (cond
-                ((eq close 41)  ; close-paren
-                 40)            ; open-paren
-                ((eq close 93)  ; close-bracket
-                 91)            ; open-bracket
+                ((eq close ?\))
+                 ?\()
+                ((eq close ?\])
+                 ?\[)
                 ((eq close ?})
                  ?{)
                 (t nil))))
-    (if (and (looking-at (string close))
+    (if (and (eq (char-after) close)
              (eq open (char-after open-pos))
-             (js2-same-line open-pos))
-        (forward-char 1)
-      (insert (string close)))
+             (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)
@@ -874,13 +10924,11 @@ Returns nil if point is not in a function.&quot;
                 (goto-char pos)
                 (js2-mode-show-element))
             (js2-mode-hide-element)))
-
          ;; //-comment?
          ((save-excursion
             (back-to-indentation)
             (looking-at js2-mode-//-comment-re))
           (js2-mode-toggle-//-comment))
-        
          ;; function?
          ((setq fn (js2-mode-function-at-point))
           (setq pos (and (js2-function-node-body fn)
@@ -978,8 +11026,8 @@ to open an individual entry.&quot;
 
 (defun js2-mode-hide-comment (n)
   (let* ((head (if (eq (js2-comment-node-format n) 'jsdoc)
-                   3                    ; /**
-                 2))                    ; /*
+                   3  ; /**
+                 2))  ; /*
          (beg (+ (js2-node-abs-pos n) head))
          (end (- (+ beg (js2-node-len n)) head 2))
          (o (js2-mode-flag-region beg end 'hide)))
@@ -1109,7 +11157,7 @@ move backward across N balanced expressions.&quot;
   (let (node end (start (point)))
     (cond
      ;; backward-sexp
-     ;; could probably make this &quot;better&quot; for some cases:
+     ;; could probably make this better for some cases:
      ;;  - if in statement block (e.g. function body), go to parent
      ;;  - infix exprs like (foo in bar) - maybe go to beginning
      ;;    of infix expr if in the right-side expression?
@@ -1174,7 +11222,7 @@ RESET means start over from the beginning.&quot;
         (if (= (point) start)
             (js2-echo-error (point) (point)))))))
 
-(defun js2-mouse-3 ()
+(defun js2-down-mouse-3 ()
   &quot;Make right-click move the point to the click location.
 This makes right-click context menu operations a bit more intuitive.
 The point will not move if the region is active, however, to avoid
@@ -1314,8 +11362,6 @@ it marks the next defun after the ones already marked.&quot;
     (unless (js2-ast-root-p fn)
       (narrow-to-region beg (+ beg (js2-node-len fn))))))
 
-(defalias 'js2r 'js2-mode-reset)
-
 (provide 'js2-mode)
 
 ;;; js2-mode.el ends here</diff>
      <filename>js2-mode/js2-mode.el</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,47 @@
+New in 20090728 release:
+  - consolidated all files into `js2-mode.el`
+  - checked js2-mode.el into GNU Emacs sources for inclusion in Emacs 23.2
+  - officially dropped support for emacs 21
+  - removed some compatibility checks (e.g. for xemacs, which is not supported)
+  - removed the restriction that js2-mode must be byte-compiled
+  - miscellaneous documentation and commenting updates
+
+New in 20090723b release:
+  - made &quot;import&quot; and &quot;export&quot; reserved words (Issue 100)
+  - fixed Issue 80 (parser confuses quotes in a regexp with a true string)
+  - fixed Issue 105 (mixed EOL confuse js2-mode)
+  - fixed Issue 87 (fontification of comments inconsistent with other modes)
+
+New for 20090723 release:
+  - support over 25 more jsdoc tags
+
+New for 20090722 release:
+
+  - added support for JavaScript 1.8 function closures
+    - set `js2-language-version' default to 180
+  - changed `js2-auto-indent-flag' to `js2-auto-indent-p'
+  - changed `js2-bounce-indent-flag' to `js2-bounce-indent-p'
+  - added js2-externs.el with some predefined browser externs
+  - added `js2-additional-externs' for user-defined externs
+  - went back to using c-fill-paragraph
+  - bug fix for matching curly braces
+  - added support for auto-completing try/catch blocks
+  - set mode-specific c-comment-start-regexp
+  - added 4-space indent option for expression continuation lines
+  - completely overhauled &quot;magic&quot;-paren logic to be less annoying
+    and more like the Eclipse behavior it was originally inspired by:
+    - autoinserted parens/brackets are now highlighted
+    - if you make an edit on a different line, all magic turns off
+    - we only auto-overwrite a paren/bracket if it's currently magic
+    - we highlight magic parens with `js2-magic-paren-face'
+    - auto-overwriting a magic delimiter makes it nonmagic
+    - auto-insert if next char on line is puncutation, or if at eol
+  - we no longer auto-insert a close-curly if the next non-whitespace
+    character in the buffer is a close-curly with an error overlay.
+  - added `js2-auto-insert-catch-block' defcustom variable,
+    to configure auto-insertion of catch-blocks
+  - prefix-arg plus open-curly =&gt; close-curly becomes magic
+
 Since 2008330:
 
   - fixed broken installation instructions</diff>
      <filename>js2-mode/release-notes.txt</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>js2-mode/js2-ast.el</filename>
    </removed>
    <removed>
      <filename>js2-mode/js2-browse.el</filename>
    </removed>
    <removed>
      <filename>js2-mode/js2-highlight.el</filename>
    </removed>
    <removed>
      <filename>js2-mode/js2-indent.el</filename>
    </removed>
    <removed>
      <filename>js2-mode/js2-messages.el</filename>
    </removed>
    <removed>
      <filename>js2-mode/js2-parse.el</filename>
    </removed>
    <removed>
      <filename>js2-mode/js2-scan.el</filename>
    </removed>
    <removed>
      <filename>js2-mode/js2-util.el</filename>
    </removed>
    <removed>
      <filename>js2-mode/js2-vars.el</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>c35f7592dae2e147464d8960114e7738df7e1657</id>
    </parent>
  </parents>
  <author>
    <name>Matthias Georgi</name>
    <email>matti.georgi@gmail.com</email>
  </author>
  <url>http://github.com/georgi/elisp/commit/a72272f7ae77acfe3a7c8c9a1a469970876980bc</url>
  <id>a72272f7ae77acfe3a7c8c9a1a469970876980bc</id>
  <committed-date>2009-09-29T17:57:39-07:00</committed-date>
  <authored-date>2009-09-29T17:57:39-07:00</authored-date>
  <message>new js2-mode</message>
  <tree>32d30b6b21c2737e2c7a8b230ebe6b3d0418741f</tree>
  <committer>
    <name>Matthias Georgi</name>
    <email>matti.georgi@gmail.com</email>
  </committer>
</commit>
