-;;; js2-mode.el --- an improved JavaScript editing mode
+;;; js2-mode.el --- Improved JavaScript editing mode
-;; Copyright (C) 2009, 2012 Free Software Foundation, Inc.
+;; Copyright (C) 2009, 2011-2013 Free Software Foundation, Inc.
-;; Author: Steve Yegge <steve.yegge@gmail.com>
-;; Contributors: mooz <stillpedant@gmail.com>
-;; Dmitry Gutov <dgutov@yandex.ru>
-;; Version: See `js2-mode-version'
-;; Keywords: languages, javascript
+;; Author: Steve Yegge <steve.yegge@gmail.com>
+;; mooz <stillpedant@gmail.com>
+;; Dmitry Gutov <dgutov@yandex.ru>
+;; URL: https://github.com/mooz/js2-mode/
+;; http://code.google.com/p/js2-mode/
+;; Version: 20130219
+;; Keywords: languages, javascript
+;; Package-Requires: ((emacs "24.1"))
;; This file is part of GNU Emacs.
;; This JavaScript editing mode supports:
;; - strict recognition of the Ecma-262 language standard
-;; - support for most Rhino and SpiderMonkey extensions from 1.5 to 1.8
+;; - support for most Rhino and SpiderMonkey extensions from 1.5 and up
;; - parsing support for ECMAScript for XML (E4X, ECMA-357)
;; - accurate syntax highlighting using a recursive-descent parser
;; - on-the-fly reporting of syntax errors and strict-mode warnings
;; - show some or all block comments as /*...*/
;; - context-sensitive menu bar and popup menus
;; - code browsing using the `imenu' package
-;; - typing helpers such as automatic insertion of matching braces/parens
;; - many customization options
;; Installation:
;;
;; To install it as your major mode for JavaScript editing:
-;; (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode))
+;; (add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
;; Alternately, to install it as a minor mode just for JavaScript linting,
;; you must add it to the appropriate major-mode hook. Normally this would be:
;; This means that `js2-mode' is currently only useful for editing JavaScript
;; files, and not for editing JavaScript within <script> tags or templates.
+;; The project page on GitHub is used for development and issue tracking.
+;; The original homepage at Google Code is mentioned here for posterity, it has
+;; outdated information and is mostly unmaintained.
+
;;; Code:
(eval-when-compile
(eval-and-compile
(require 'cc-mode) ; (only) for `c-populate-syntax-table'
- (require 'cc-langs) ; it's here in Emacs 21...
(require 'cc-engine)) ; for `c-paragraph-start' et. al.
(defvar electric-layout-rules)
(defvar js2-ecma-262-externs
(mapcar 'symbol-name
- '(Array Boolean Date Error EvalError Function Infinity
+ '(Array Boolean Date Error EvalError Function Infinity JSON
Math NaN Number Object RangeError ReferenceError RegExp
String SyntaxError TypeError URIError arguments
decodeURI decodeURIComponent encodeURI
"Mozilla Rhino externs.
Set `js2-include-rhino-externs' to t to include them.")
-(defvar js2-gears-externs
+(defvar js2-node-externs
(mapcar 'symbol-name
- '(
- ;; TODO(stevey): add these
- ))
- "Google Gears externs.
-Set `js2-include-gears-externs' to t to include them.")
+ '(__dirname __filename Buffer clearInterval clearTimeout require
+ console exports global module process setInterval setTimeout))
+ "Node.js externs.
+Set `js2-include-node-externs' to t to include them.")
;;; Variables
(defcustom js2-highlight-level 2
"Amount of syntax highlighting to perform.
-0 or a negative value means do no highlighting.
+0 or a negative value means none.
1 adds basic syntax highlighting.
2 adds highlighting of some Ecma built-in properties.
3 adds highlighting of many Ecma built-in functions."
:type 'integer)
(js2-mark-safe-local 'js2-basic-offset 'integerp)
-
(defcustom js2-bounce-indent-p nil
"Non-nil to have indent-line function choose among alternatives.
If nil, the indent-line function will indent to a predetermined column
:type 'boolean
:group 'js2-mode)
-(defcustom js2-consistent-level-indent-inner-bracket-p t
- "Non-nil to make indentation level inner bracket consistent,
-regardless of the beginning bracket position."
- :group 'js2-mode
- :type 'boolean)
-(js2-mark-safe-local 'js2-consistent-level-indent-inner-bracket-p 'booleanp)
+(defcustom js2-pretty-multiline-declarations t
+ "Non-nil to line up multiline declarations vertically:
-(defcustom js2-pretty-multiline-decl-indentation-p t
- "Non-nil to line up multiline declarations vertically. See the
-function `js2-multiline-decl-indentation' for details."
- :group 'js2-mode
- :type 'boolean)
-(js2-mark-safe-local 'js2-pretty-multiline-decl-indentation-p 'booleanp)
+ var a = 10,
+ b = 20,
+ c = 30;
-(defcustom js2-always-indent-assigned-expr-in-decls-p nil
- "If both `js2-pretty-multiline-decl-indentation-p' and this are non-nil,
-always additionally indent function expression or array/object literal
-assigned in a declaration, even when only one var is declared."
- :group 'js2-mode
- :type 'boolean)
-(js2-mark-safe-local 'js2-always-indent-assigned-expr-in-decls-p 'booleanp)
-
-(defcustom js2-indent-on-enter-key nil
- "Non-nil to have Enter/Return key indent the line.
-This is unusual for Emacs modes but common in IDEs like Eclipse."
- :type 'boolean
- :group 'js2-mode)
+If the value is not `all', and the first assigned value in
+declaration is a function/array/object literal spanning several
+lines, it won't be indented additionally:
-(defcustom js2-enter-indents-newline nil
- "Non-nil to have Enter/Return key indent the newly-inserted line.
-This is unusual for Emacs modes but common in IDEs like Eclipse."
- :type 'boolean
- :group 'js2-mode)
+ var o = { var bar = 2,
+ foo: 3 vs. o = {
+ }, foo: 3
+ bar = 2; };"
+ :group 'js2-mode
+ :type 'symbol)
+(js2-mark-safe-local 'js2-pretty-multiline-declarations 'symbolp)
(defcustom js2-idle-timer-delay 0.2
"Delay in secs before re-parsing after user makes changes.
:type 'number
:group 'js2-mode)
-(defcustom js2-mode-escape-quotes t
- "Non-nil to disable automatic quote-escaping inside strings."
- :type 'boolean
- :group 'js2-mode)
-
-(defcustom js2-mode-squeeze-spaces t
- "Non-nil to normalize whitespace when filling in comments.
-Multiple runs of spaces are converted to a single space."
- :type 'boolean
+(defcustom js2-concat-multiline-strings t
+ "Non-nil to automatically turn a newline in mid-string into a
+string concatenation. When `eol', the '+' will be inserted at the
+end of the line, otherwise, at the beginning of the next line."
+ :type '(choice (const t) (const eol) (const nil))
:group 'js2-mode)
(defcustom js2-mode-show-parse-errors t
(defcustom js2-strict-trailing-comma-warning t
"Non-nil to warn about trailing commas in array literals.
-Ecma-262 forbids them, but many browsers permit them. IE is the
-big exception, and can produce bugs if you have trailing commas."
+Ecma-262-5.1 allows them, but older versions of IE raise an error."
:type 'boolean
:group 'js2-mode)
:type 'boolean
:group 'js2-mode)
-(defcustom js2-strict-cond-assign-warning t
- "Non-nil to warn about expressions like if (a = b).
-This often should have been '==' instead of '='. If the warning
-is enabled, you can suppress it on a per-expression basis by
-parenthesizing the expression, e.g. if ((a = b)) ..."
- :type 'boolean
- :group 'js2-mode)
-
(defcustom js2-strict-var-redeclaration-warning t
"Non-nil to warn about redeclaring variables in a script or function."
:type 'boolean
:type 'boolean
:group 'js2-mode)
-(defcustom js2-language-version 180
+(defcustom js2-language-version 200
"Configures what JavaScript language version to recognize.
-Currently versions 150, 160, 170 and 180 are supported, corresponding
-to JavaScript 1.5, 1.6, 1.7 and 1.8, respectively. In a nutshell,
-1.6 adds E4X support, 1.7 adds let, yield, and Array comprehensions,
-and 1.8 adds function closures."
+Currently versions 150, 160, 170, 180 and 200 are supported,
+corresponding to JavaScript 1.5, 1.6, 1.7, 1.8 and 2.0 (Harmony),
+respectively. In a nutshell, 1.6 adds E4X support, 1.7 adds let,
+yield, and Array comprehensions, and 1.8 adds function closures."
:type 'integer
:group 'js2-mode)
var foo = {int: 5, while: 6, continue: 7};
foo.return = 8;
-Ecma-262 forbids this syntax, but many browsers support it."
+Ecma-262 5.1 allows this syntax, but some engines still don't."
:type 'boolean
:group 'js2-mode)
:type 'boolean
:group 'js2-mode)
-(defvar js2-mode-version 20120416
- "Release number for `js2-mode'.")
-
;; scanner variables
(defmacro js2-deflocal (name value &optional comment)
(defvar js2-COMMENT 160)
(defvar js2-ENUM 161) ; for "enum" reserved word
+(defvar js2-TRIPLEDOT 162) ; for rest parameter
-(defconst js2-num-tokens (1+ js2-ENUM))
+(defconst js2-num-tokens (1+ js2-TRIPLEDOT))
(defconst js2-debug-print-trees nil)
(js2-deflocal js2-recorded-identifiers nil
"Tracks identifiers found during parsing.")
-(defmacro js2-in-lhs (body)
- `(let ((js2-is-in-lhs t))
- ,body))
-
-(defmacro js2-in-rhs (body)
- `(let ((js2-is-in-lhs nil))
- ,body))
-
-(js2-deflocal js2-is-in-lhs nil
- "True while parsing lhs statement")
+(js2-deflocal js2-is-in-destructuring nil
+ "True while parsing destructuring expression.")
(defcustom js2-global-externs nil
"A list of any extern names you'd like to consider always declared.
which only worries about top-level (unqualified) references.
As js2-mode's processing improves, we will flesh out this list.
-The initial value is set to `js2-ecma-262-externs', unless you
-have set `js2-include-browser-externs', in which case the browser
-externs are also included.
+The initial value is set to `js2-ecma-262-externs', unless some
+of the `js2-include-?-externs' variables are set to t, in which
+case the browser, Rhino and/or Node.js externs are also included.
See `js2-additional-externs' for more information.")
"Non-nil to include browser externs in the master externs list.
If you work on JavaScript files that are not intended for browsers,
such as Mozilla Rhino server-side JavaScript, set this to nil.
-You can always include them on a per-file basis by calling
-`js2-add-browser-externs' from a function on `js2-mode-hook'.
-
See `js2-additional-externs' for more information about externs."
:type 'boolean
:group 'js2-mode)
-(defcustom js2-include-rhino-externs t
+(defcustom js2-include-rhino-externs nil
"Non-nil to include Mozilla Rhino externs in the master externs list.
See `js2-additional-externs' for more information about externs."
:type 'boolean
:group 'js2-mode)
-(defcustom js2-include-gears-externs t
- "Non-nil to include Google Gears externs in the master externs list.
+(defcustom js2-include-node-externs nil
+ "Non-nil to include Node.js externs in the master externs list.
See `js2-additional-externs' for more information about externs."
:type 'boolean
:group 'js2-mode)
These should go in `js2-additional-externs', which is buffer-local.
Finally, you can add a function to `js2-post-parse-callbacks',
-which is called after parsing completes, and `root' is bound to
+which is called after parsing completes, and `js2-mode-ast' is bound to
the root of the parse tree. At this stage you can set up an AST
node visitor using `js2-visit-ast' and examine the parse tree
for specific import patterns that may imply the existence of
called after the new parse tree is built. This can take some time
in large files.")
-(defface js2-warning-face
+(defface js2-warning
`((((class color) (background light))
(:underline "orange"))
(((class color) (background dark))
"Face for JavaScript warnings."
:group 'js2-mode)
-(defface js2-error-face
+(defface js2-error
`((((class color) (background light))
(:foreground "red"))
(((class color) (background dark))
"Face for JavaScript errors."
:group 'js2-mode)
-(defface js2-jsdoc-tag-face
+(defface js2-jsdoc-tag
'((t :foreground "SlateGray"))
"Face used to highlight @whatever tags in jsdoc comments."
:group 'js2-mode)
-(defface js2-jsdoc-type-face
+(defface js2-jsdoc-type
'((t :foreground "SteelBlue"))
"Face used to highlight {FooBar} types in jsdoc comments."
:group 'js2-mode)
-(defface js2-jsdoc-value-face
+(defface js2-jsdoc-value
'((t :foreground "PeachPuff3"))
"Face used to highlight tag values in jsdoc comments."
:group 'js2-mode)
-(defface js2-function-param-face
+(defface js2-function-param
'((t :foreground "SeaGreen"))
"Face used to highlight function parameters in javascript."
:group 'js2-mode)
-(defface js2-instance-member-face
+(defface js2-instance-member
'((t :foreground "DarkOrchid"))
"Face used to highlight instance variables in javascript.
Not currently used."
:group 'js2-mode)
-(defface js2-private-member-face
+(defface js2-private-member
'((t :foreground "PeachPuff3"))
"Face used to highlight calls to private methods in javascript.
Not currently used."
:group 'js2-mode)
-(defface js2-private-function-call-face
+(defface js2-private-function-call
'((t :foreground "goldenrod"))
"Face used to highlight calls to private functions in javascript.
Not currently used."
:group 'js2-mode)
-(defface js2-jsdoc-html-tag-name-face
+(defface js2-jsdoc-html-tag-name
'((((class color) (min-colors 88) (background light))
(:foreground "rosybrown"))
(((class color) (min-colors 8) (background dark))
"Face used to highlight jsdoc html tag names"
:group 'js2-mode)
-(defface js2-jsdoc-html-tag-delimiter-face
+(defface js2-jsdoc-html-tag-delimiter
'((((class color) (min-colors 88) (background light))
(:foreground "dark khaki"))
(((class color) (min-colors 8) (background dark))
"Face used to highlight brackets in jsdoc html tags."
:group 'js2-mode)
+(defface js2-external-variable
+ '((t :foreground "orange"))
+ "Face used to highlight undeclared variable identifiers.")
(defcustom js2-post-parse-callbacks nil
"A list of callback functions invoked after parsing finishes.
:type 'list
:group 'js2-mode)
-(defface js2-external-variable-face
- '((t :foreground "orange"))
- "Face used to highlight undeclared variable identifiers.
+(defcustom js2-highlight-external-variables t
+ "Non-nil to highlight undeclared variable identifiers.
An undeclared variable is any variable not declared with var or let
in the current scope or any lexically enclosing scope. If you use
such a variable, then you are either expecting it to originate from
another file, or you've got a potential bug."
- :group 'js2-mode)
-
-(defcustom js2-highlight-external-variables t
- "Non-nil to highlight undeclared variable identifiers."
:type 'boolean
:group 'js2-mode)
(let ((map (make-sparse-keymap))
keys)
(define-key map [mouse-1] #'js2-mode-show-node)
+ (define-key map (kbd "M-j") #'js2-line-break)
(define-key map (kbd "C-c C-e") #'js2-mode-hide-element)
(define-key map (kbd "C-c C-s") #'js2-mode-show-element)
(define-key map (kbd "C-c C-a") #'js2-mode-show-all)
;; TODO(stevey): construct this table at compile-time.
(defmacro js2-msg (key &rest strings)
- `(puthash ,key (funcall #'concat ,@strings)
+ `(puthash ,key (concat ,@strings)
js2-message-table))
(defun js2-get-msg (msg-key)
(js2-msg "msg.no.paren.after.parms"
"missing ) after formal parameters")
+(js2-msg "msg.no.default.after.default.param" ; added by js2-mode
+ "parameter without default follows parameter with default")
+
+(js2-msg "msg.param.after.rest" ; added by js2-mode
+ "parameter after rest parameter")
+
(js2-msg "msg.no.brace.body"
"missing '{' before function body")
"missing ; after for-loop condition")
(js2-msg "msg.in.after.for.name"
- "missing in after for")
+ "missing in or of after for")
(js2-msg "msg.no.paren.for.ctrl"
"missing ) after for-loop control")
"Code has no side effects")
(js2-msg "msg.extra.trailing.comma"
- "Trailing comma is not legal in an ECMA-262 object initializer")
+ "Trailing comma is not supported in some browsers")
(js2-msg "msg.array.trailing.comma"
"Trailing comma yields different behavior across browsers")
(js2-msg "msg.assn.create.strict"
"Assignment to undeclared variable %s")
+(js2-msg "msg.undeclared.variable" ; added by js2-mode
+ "Undeclared variable or function '%s'")
+
(js2-msg "msg.ref.undefined.prop"
"Reference to undefined property '%s'")
(current-column))
js2-ts-hit-eof))))
-(defun js2-report-warning (msg &optional msg-arg pos len)
+(defun js2-report-warning (msg &optional msg-arg pos len face)
(if js2-compiler-report-warning-as-error
(js2-report-error msg msg-arg pos len)
(push (list (list msg msg-arg)
(or pos js2-token-beg)
- (or len (- js2-token-end js2-token-beg)))
+ (or len (- js2-token-end js2-token-beg))
+ face)
js2-parsed-warnings)))
(defun js2-add-strict-warning (msg-id &optional msg-arg beg end)
object
in-pos
each-pos
- foreach-p lp
- rp)))
+ foreach-p forof-p
+ lp rp)))
"AST node for a for..in loop."
iterator ; [var] foo in ...
object ; object over which we're iterating
in-pos ; buffer position of 'in' keyword
each-pos ; buffer position of 'each' keyword, if foreach-p
- foreach-p) ; t if it's a for-each loop
+ foreach-p ; t if it's a for-each loop
+ forof-p) ; t if it's a for-of loop
(put 'cl-struct-js2-for-in-node 'js2-visitor 'js2-visit-for-in-node)
(put 'cl-struct-js2-for-in-node 'js2-printer 'js2-print-for-in-node)
(defun js2-print-for-in-node (n i)
(let ((pad (js2-make-pad i))
- (foreach (js2-for-in-node-foreach-p n)))
+ (foreach (js2-for-in-node-foreach-p n))
+ (forof (js2-for-in-node-forof-p n)))
(insert pad "for ")
(if foreach
(insert "each "))
(insert "(")
(js2-print-ast (js2-for-in-node-iterator n) 0)
- (insert " in ")
+ (if forof
+ (insert " of ")
+ (insert " in "))
(js2-print-ast (js2-for-in-node-object n) 0)
(insert ") {\n")
(js2-print-body (js2-for-in-node-body n) (1+ i))
(ftype 'FUNCTION)
(form 'FUNCTION_STATEMENT)
(name "")
- params body
+ params rest-p
+ body
lp rp)))
"AST node for a function declaration.
The `params' field is a Lisp list of nodes. Each node is either a simple
form ; FUNCTION_{STATEMENT|EXPRESSION|EXPRESSION_STATEMENT}
name ; function name (a `js2-name-node', or nil if anonymous)
params ; a Lisp list of destructuring forms or simple name nodes
+ rest-p ; if t, the last parameter is rest parameter
body ; a `js2-block-node' or expression node (1.8 only)
lp ; position of arg-list open-paren, or nil if omitted
rp ; position of arg-list close-paren, or nil if omitted
(getter (js2-node-get-prop n 'GETTER_SETTER))
(name (js2-function-node-name n))
(params (js2-function-node-params n))
+ (rest-p (js2-function-node-rest-p n))
(body (js2-function-node-body n))
(expr (eq (js2-function-node-form n) 'FUNCTION_EXPRESSION)))
(unless getter
for param in params
for count from 1
do
+ (when (and rest-p (= count len))
+ (insert "..."))
(js2-print-ast param 0)
- (if (< count len)
- (insert ", ")))
+ (when (< count len)
+ (insert ", ")))
(insert ") {")
(unless expr
(insert "\n"))
(insert pad)
(insert (cond
((= tt js2-VAR) "var ")
- ((= tt js2-LET) "") ; handled by parent let-{expr/stmt}
+ ((= tt js2-LET) "let ")
((= tt js2-CONST) "const ")
(t
(error "malformed var-decl node"))))
(defun js2-print-object-prop-node (n i)
(insert (js2-make-pad i))
(js2-print-ast (js2-object-prop-node-left n) 0)
- (insert ":")
+ (insert ": ")
(js2-print-ast (js2-object-prop-node-right n) 0))
(defstruct (js2-getter-setter-node
(js2-print-ast l 0))
(when filter
(insert " if (")
- (js2-print-ast filter 0))
- (insert ")]")))
+ (js2-print-ast filter 0)
+ (insert ")"))
+ (insert "]")))
(defstruct (js2-array-comp-loop-node
(:include js2-for-in-node)
object in-pos
foreach-p
each-pos
+ forof-p
lp rp)))
"AST subtree for each 'for (foo in bar)' loop in an array comprehension.")
(js2-visit-ast (js2-array-comp-loop-node-object n) v))
(defun js2-print-array-comp-loop (n i)
- (insert "for (")
+ (insert "for ")
+ (when (js2-array-comp-loop-node-foreach-p n) (insert "each "))
+ (insert "(")
(js2-print-ast (js2-array-comp-loop-node-iterator n) 0)
- (insert " in ")
+ (if (js2-array-comp-loop-node-forof-p n)
+ (insert " of ")
+ (insert " in "))
(js2-print-ast (js2-array-comp-loop-node-object n) 0)
(insert ")"))
Note that the position may be nil in the case of a parse error."
(cond
((js2-elem-get-node-p node)
- (js2-elem-get-node-lb node))
+ (js2-elem-get-node-rb node))
((js2-loop-node-p node)
(js2-loop-node-rp node))
((js2-function-node-p node)
(js2-node-parent-script-or-fn
(js2-node-parent-script-or-fn node))))))
-(defun js2-function-param-node-p (node)
- "Return non-nil if NODE is a param node of a `js2-function-node'."
- (let ((parent (js2-node-parent node)))
- (and parent
- (js2-function-node-p parent)
- (memq node (js2-function-node-params parent)))))
-
(defun js2-mode-shift-kids (kids start offset)
(dolist (kid kids)
(if (> (js2-node-pos kid) start)
for arg in args
for count from 1
do
- (js2-print-ast arg 0)
+ (when arg (js2-print-ast arg 0))
(if (< count len)
(insert (or delimiter ", ")))))
do
(unless (or (memq sym '(js2-EOF_CHAR js2-ERROR))
(not (boundp sym)))
- (aset names (symbol-value sym) ; code, e.g. 152
- (substring (symbol-name sym) 4)) ; name, e.g. "LET"
+ (aset names (symbol-value sym) ; code, e.g. 152
+ (downcase
+ (substring (symbol-name sym) 4))) ; name, e.g. "let"
(push sym js2-tokens)))
names)
"Vector mapping int values to token string names, sans `js2-' prefix.")
(defconst js2-token-codes
(let ((table (make-hash-table :test 'eq :size 256)))
(loop for name across js2-token-names
- for sym = (intern (concat "js2-" name))
+ for sym = (intern (concat "js2-" (upcase name)))
do
(puthash sym (symbol-value sym) table))
;; clean up a few that are "wrong" in Rhino's token codes
(throw 'return js2-COLON)))
(?.
(if (js2-match-char ?.)
- (js2-ts-return js2-DOTDOT)
+ (if (js2-match-char ?.)
+ (js2-ts-return js2-TRIPLEDOT)
+ (js2-ts-return js2-DOTDOT))
(if (js2-match-char ?\()
(js2-ts-return js2-DOTQUERY)
(throw 'return js2-DOT))))
"toTimeString" "toUTCString"
;; properties of the RegExp prototype object
"exec" "test"
+ ;; properties of the JSON prototype object
+ "parse" "stringify"
;; SpiderMonkey/Rhino extensions, versions 1.5+
"toSource" "__defineGetter__" "__defineSetter__"
"__lookupGetter__" "__lookupSetter__" "__noSuchMethod__"
(js2-set-face (setq pos (+ (js2-node-pos parent) ; absolute
(js2-node-pos prop))) ; relative
(+ pos (js2-node-len prop))
- face)))))
+ face 'record)))))
(defun js2-parse-highlight-member-expr-node (node)
"Perform syntax highlighting of EcmaScript built-in properties.
(when face
(setq pos (js2-node-pos node)
end (+ pos (js2-node-len node)))
- (js2-set-face pos end face))))
+ (js2-set-face pos end face 'record))))
;; case 2: property access or function call
((or (js2-prop-get-node-p node)
;; highlight function call if expr is a prop-get node
(defun js2-jsdoc-highlight-helper ()
(js2-set-face (match-beginning 1)
(match-end 1)
- 'js2-jsdoc-tag-face)
+ 'js2-jsdoc-tag)
(if (match-beginning 2)
(if (save-excursion
(goto-char (match-beginning 2))
(= (char-after) ?{))
(js2-set-face (1+ (match-beginning 2))
(1- (match-end 2))
- 'js2-jsdoc-type-face)
+ 'js2-jsdoc-type)
(js2-set-face (match-beginning 2)
(match-end 2)
- 'js2-jsdoc-value-face)))
+ 'js2-jsdoc-value)))
(if (match-beginning 3)
(js2-set-face (match-beginning 3)
(match-end 3)
- 'js2-jsdoc-value-face)))
+ 'js2-jsdoc-value)))
(defun js2-highlight-jsdoc (ast)
"Highlight doc comment tags."
(while (re-search-forward js2-jsdoc-html-tag-regexp nil t)
(js2-set-face (match-beginning 1)
(match-end 1)
- 'js2-jsdoc-html-tag-delimiter-face)
+ 'js2-jsdoc-html-tag-delimiter)
(js2-set-face (match-beginning 2)
(match-end 2)
- 'js2-jsdoc-html-tag-name-face)
+ 'js2-jsdoc-html-tag-name)
(js2-set-face (match-beginning 3)
(match-end 3)
- 'js2-jsdoc-html-tag-delimiter-face))))))))
+ 'js2-jsdoc-html-tag-delimiter))))))))
(defun js2-highlight-assign-targets (node left right)
"Highlight function properties and external variables."
(member name js2-default-externs)
(member name js2-additional-externs)
(js2-get-defining-scope scope name))
- (js2-set-face pos end 'js2-external-variable-face 'record)
- (js2-record-text-property pos end 'help-echo "Undeclared variable")
- (js2-record-text-property pos end 'point-entered #'js2-echo-help))))
+ (js2-report-warning "msg.undeclared.variable" name pos (- end pos)
+ 'js2-external-variable))))
(setq js2-recorded-identifiers nil)))
;;; IMenu support
(defun js2-record-imenu-functions (node &optional var)
"Record function definitions for imenu.
NODE is a function node or an object literal.
-VAR, if non-nil, is the expression that NODE is being assigned to."
+VAR, if non-nil, is the expression that NODE is being assigned to.
+When passed arguments of wrong type, does nothing."
(when js2-parse-ide-mode
(let ((fun-p (js2-function-node-p node))
qname left fname-node pos)
(t t))))
(defun js2-wrapper-function-p (node)
- "Returns t if NODE is a function expression that's immediately invoked.
+ "Return t if NODE is a function expression that's immediately invoked.
NODE must be `js2-function-node'."
(let ((parent (js2-node-parent node)))
(or
(dolist (entry entries)
;; function node goes first
(destructuring-bind (current-fn &rest (&whole chain head &rest)) entry
- ;; examine its defining scope;
- ;; if top-level/external, keep as-is
- (if (js2-node-top-level-decl-p head)
+ ;; Examine head's defining scope:
+ ;; Pre-processed chain, or top-level/external, keep as-is.
+ (if (or (stringp head) (js2-node-top-level-decl-p head))
(push chain result)
(when (js2-this-node-p head)
(setq chain (cdr chain))) ; discard this-node
;;; Parser
-(defconst js2-version "1.8.0"
- "Version of JavaScript supported, plus minor js2 version.")
+(defconst js2-version "1.8.5"
+ "Version of JavaScript supported.")
(defmacro js2-record-face (face)
"Record a style run of FACE for the current token."
(when js2-parse-ide-mode
(cond
((minusp tt)
- (js2-record-face 'js2-error-face))
+ (js2-record-face 'js2-error))
((setq face (aref js2-kwd-tokens tt))
(js2-record-face face))
((and (= tt js2-NAME)
tt))) ; return unflagged token
(defun js2-peek-flagged-token ()
- "Returns the current token along with any flags set for it."
+ "Return the current token along with any flags set for it."
(js2-peek-token)
js2-current-flagged-token)
(js2-consume-token)
t))
+(defun js2-match-contextual-kwd (name)
+ "Consume and return t if next token is `js2-NAME', and its
+string is NAME. Returns nil and does nothing otherwise."
+ (if (or (/= (js2-peek-token) js2-NAME)
+ (not (string= js2-ts-string name)))
+ nil
+ (js2-consume-token)
+ (js2-record-face 'font-lock-keyword-face)
+ t))
+
(defun js2-valid-prop-name-token (tt)
(or (= tt js2-NAME)
- (and js2-allow-keywords-as-property-names
- (plusp tt)
- (aref js2-kwd-tokens tt))))
+ (when (and js2-allow-keywords-as-property-names
+ (plusp tt)
+ (aref js2-kwd-tokens tt))
+ (js2-save-name-token-data js2-token-beg (js2-token-name tt))
+ t)))
(defun js2-match-prop-name ()
"Consume token and return t if next token is a valid property name.
(push comment (js2-ast-root-comments root))
(js2-node-add-children root comment)))
(setf (js2-node-len root) (- end pos))
+ (setq js2-mode-ast root) ; Make sure this is available for callbacks.
;; Give extensions a chance to muck with things before highlighting starts.
(let ((js2-additional-externs js2-additional-externs))
- (dolist (callback js2-post-parse-callbacks)
- (funcall callback))
+ (save-excursion
+ (dolist (callback js2-post-parse-callbacks)
+ (funcall callback)))
(js2-highlight-undeclared-vars))
root))
(defun js2-parse-function-params (fn-node pos)
(if (js2-match-token js2-RP)
(setf (js2-function-node-rp fn-node) (- js2-token-beg pos))
- (let (params len param)
+ (let (params len param default-found rest-param-at)
(loop for tt = (js2-peek-token)
do
(cond
;; destructuring param
((or (= tt js2-LB) (= tt js2-LC))
- (setq param (js2-parse-primary-expr-lhs))
+ (when default-found
+ (js2-report-error "msg.no.default.after.default.param"))
+ (setq param (js2-parse-destruct-primary-expr))
(js2-define-destruct-symbols param
js2-LP
- 'js2-function-param-face)
+ 'js2-function-param)
(push param params))
- ;; simple name
+ ;; variable name
(t
+ (when (and (>= js2-language-version 200)
+ (js2-match-token js2-TRIPLEDOT)
+ (not rest-param-at))
+ ;; to report errors if there are more parameters
+ (setq rest-param-at (length params)))
(js2-must-match js2-NAME "msg.no.parm")
- (js2-record-face 'js2-function-param-face)
+ (js2-record-face 'js2-function-param)
(setq param (js2-create-name-node))
(js2-define-symbol js2-LP js2-ts-string param)
+ ;; default parameter value
+ (when (or (and default-found
+ (not rest-param-at)
+ (js2-must-match js2-ASSIGN
+ "msg.no.default.after.default.param"
+ (js2-node-pos param)
+ (js2-node-len param)))
+ (and (>= js2-language-version 200)
+ (js2-match-token js2-ASSIGN)))
+ (let* ((pos (js2-node-pos param))
+ (tt js2-current-token)
+ (op-pos (- js2-token-beg pos))
+ (left param)
+ (right (js2-parse-assign-expr))
+ (len (- (js2-node-end right) pos)))
+ (setq param (make-js2-assign-node
+ :type tt :pos pos :len len :op-pos op-pos
+ :left left :right right)
+ default-found t)
+ (js2-node-add-children param left right)))
(push param params)))
+ (when (and rest-param-at (> (length params) (1+ rest-param-at)))
+ (js2-report-error "msg.param.after.rest" nil
+ (js2-node-pos param) (js2-node-len param)))
while
(js2-match-token js2-COMMA))
- (if (js2-must-match js2-RP "msg.no.paren.after.parms")
- (setf (js2-function-node-rp fn-node) (- js2-token-beg pos)))
+ (when (js2-must-match js2-RP "msg.no.paren.after.parms")
+ (setf (js2-function-node-rp fn-node) (- js2-token-beg pos)))
+ (when rest-param-at
+ (setf (js2-function-node-rest-p fn-node) t))
(dolist (p params)
(js2-node-add-children fn-node p)
(push p (js2-function-node-params fn-node))))))
"Parser for for-statement. Last matched token must be js2-FOR.
Parses for, for-in, and for each-in statements."
(let ((for-pos js2-token-beg)
- pn is-for-each is-for-in in-pos each-pos tmp-pos
+ pn is-for-each is-for-in-or-of is-for-of
+ in-pos each-pos tmp-pos
init ; Node init is also foo in 'foo in object'
cond ; Node cond is also object in 'foo in object'
incr ; 3rd section of for-loop initializer
(setq init (js2-parse-variables tt js2-token-beg)))
(t
(setq init (js2-parse-expr)))))
- (if (js2-match-token js2-IN)
- (setq is-for-in t
+ (if (or (js2-match-token js2-IN)
+ (and (>= js2-language-version 200)
+ (js2-match-contextual-kwd "of")
+ (setq is-for-of t)))
+ (setq is-for-in-or-of t
in-pos (- js2-token-beg for-pos)
;; scope of iteration target object is not the scope we've created above.
;; stash current scope temporary.
(js2-parse-expr))))
(if (js2-must-match js2-RP "msg.no.paren.for.ctrl")
(setq rp (- js2-token-beg for-pos)))
- (if (not is-for-in)
+ (if (not is-for-in-or-of)
(setq pn (make-js2-for-node :init init
:condition cond
:update incr
:in-pos in-pos
:foreach-p is-for-each
:each-pos each-pos
+ :forof-p is-for-of
:lp lp
:rp rp)))
(unwind-protect
;; destructuring pattern
;; catch ({ message, file }) { ... }
((or (= tt js2-LB) (= tt js2-LC))
- (setq param
- (js2-define-destruct-symbols (js2-parse-primary-expr-lhs)
- js2-LET nil)))
+ (setq param (js2-parse-destruct-primary-expr))
+ (js2-define-destruct-symbols param js2-LET nil))
;; simple name
(t
(js2-must-match js2-NAME "msg.bad.catchcond")
init nil)
(if (or (= tt js2-LB) (= tt js2-LC))
;; Destructuring assignment, e.g., var [a, b] = ...
- (setq destructuring (js2-parse-primary-expr-lhs)
+ (setq destructuring (js2-parse-destruct-primary-expr)
end (js2-node-end destructuring))
;; Simple variable name
(when (js2-must-match js2-NAME "msg.bad.var")
(when (js2-match-token js2-ASSIGN)
(setq init (js2-parse-assign-expr)
end (js2-node-end init))
- (if (and js2-parse-ide-mode
- (or (js2-object-node-p init)
- (js2-function-node-p init)))
- (js2-record-imenu-functions init name)))
+ (js2-record-imenu-functions init name))
(when name
(js2-set-face nbeg nend (if (js2-function-node-p init)
'font-lock-function-name-face
:right right))
(when js2-parse-ide-mode
(js2-highlight-assign-targets pn left right)
- (if (or (js2-function-node-p right)
- (js2-object-node-p right))
- (js2-record-imenu-functions right left)))
+ (js2-record-imenu-functions right left))
;; do this last so ide checks above can use absolute positions
(js2-node-add-children pn left right))
pn)))
:rb (js2-relpos rb pos)))
(js2-node-add-children pn namespace expr))))
-(defun js2-parse-primary-expr-lhs ()
- (let ((js2-is-in-lhs t))
+(defun js2-parse-destruct-primary-expr ()
+ (let ((js2-is-in-destructuring t))
(js2-parse-primary-expr)))
(defun js2-parse-primary-expr ()
(after-lb-or-comma t)
after-comma tt elems pn
(continue t))
- (unless js2-is-in-lhs
+ (unless js2-is-in-destructuring
(js2-push-scope (make-js2-scope))) ; for array comp
(while continue
(setq tt (js2-peek-token))
:len (- js2-ts-cursor pos)
:elems (nreverse elems)))
(apply #'js2-node-add-children pn (js2-array-node-elems pn))
- (when (and after-comma (not js2-is-in-lhs))
+ (when (and after-comma (not js2-is-in-destructuring))
(js2-parse-warn-trailing-comma "msg.array.trailing.comma"
pos elems after-comma)))
;; destructuring binding
- (js2-is-in-lhs
+ (js2-is-in-destructuring
(push (if (or (= tt js2-LC)
(= tt js2-LB)
(= tt js2-NAME))
;; [a, b, c] | {a, b, c} | {a:x, b:y, c:z} | a
- (js2-parse-primary-expr-lhs)
+ (js2-parse-destruct-primary-expr)
;; invalid pattern
(js2-consume-token)
(js2-report-error "msg.bad.var")
(push (js2-parse-assign-expr) elems)
(setq after-lb-or-comma nil
after-comma nil))))
- (unless js2-is-in-lhs
+ (unless js2-is-in-destructuring
(js2-pop-scope))
pn))
result))
(defun js2-parse-array-comp-loop ()
- "Parse a 'for [each] (foo in bar)' expression in an Array comprehension.
+ "Parse a 'for [each] (foo [in|of] bar)' expression in an Array comprehension.
Last token peeked should be the initial FOR."
(let ((pos js2-token-beg)
(pn (make-js2-array-comp-loop-node))
- tt iter obj foreach-p in-pos each-pos lp rp)
+ tt iter obj foreach-p forof-p in-pos each-pos lp rp)
(assert (= (js2-next-token) js2-FOR)) ; consumes token
(js2-push-scope pn)
(unwind-protect
(cond
((or (= tt js2-LB)
(= tt js2-LC))
- ;; handle destructuring assignment
- (setq iter (js2-parse-primary-expr-lhs))
+ (setq iter (js2-parse-destruct-primary-expr))
(js2-define-destruct-symbols iter js2-LET
'font-lock-variable-name-face t))
- ((js2-valid-prop-name-token tt)
- (js2-consume-token)
+ ((js2-match-token js2-NAME)
(setq iter (js2-create-name-node)))
(t
(js2-report-error "msg.bad.var")))
;; be restricted to the array comprehension
(if (js2-name-node-p iter)
(js2-define-symbol js2-LET (js2-name-node-name iter) pn t))
- (if (js2-must-match js2-IN "msg.in.after.for.name")
- (setq in-pos (- js2-token-beg pos)))
+ (if (or (js2-match-token js2-IN)
+ (and (>= js2-language-version 200)
+ (js2-match-contextual-kwd "of")
+ (setq forof-p t)))
+ (setq in-pos (- js2-token-beg pos))
+ (js2-report-error "msg.in.after.for.name"))
(setq obj (js2-parse-expr))
(if (js2-must-match js2-RP "msg.no.paren.for.ctrl")
(setq rp (- js2-token-beg pos)))
(js2-array-comp-loop-node-in-pos pn) in-pos
(js2-array-comp-loop-node-each-pos pn) each-pos
(js2-array-comp-loop-node-foreach-p pn) foreach-p
+ (js2-array-comp-loop-node-forof-p pn) forof-p
(js2-array-comp-loop-node-lp pn) lp
(js2-array-comp-loop-node-rp pn) rp)
(js2-node-add-children pn iter obj))
(defun js2-parse-named-prop (tt)
"Parse a name, string, or getter/setter object property.
-When `js2-is-in-lhs' is t, forms like {a, b, c} will be permitted."
+When `js2-is-in-destructuring' is t, forms like {a, b, c} will be permitted."
(js2-consume-token)
(let ((string-prop (and (= tt js2-STRING)
(make-js2-string-node)))
(js2-record-face 'font-lock-function-name-face) ; for peeked name
(setq name (js2-create-name-node)) ; discard get/set & use peeked name
(js2-parse-getter-setter-prop ppos name (string= prop "get")))
- ;; abbreviated destructuring bind e.g., {a, b} = c;
- ;; XXX: To be honest, the value of `js2-is-in-lhs' becomes t only when
- ;; patterns are appeared in variable declaration, function parameters, and catch-clause.
- ;; We have to set t to `js2-is-in-lhs' when the current expressions are part of any
- ;; assignment but it's difficult because it requires looking ahead of expression.
- ((and js2-is-in-lhs
+ ;; Abbreviated destructuring binding, e.g. {a, b} = c;
+ ;; XXX: To be honest, the value of `js2-is-in-destructuring' becomes t only
+ ;; when patterns are used in variable declarations, function parameters,
+ ;; catch-clause, and iterators.
+ ;; We have to set `js2-is-in-destructuring' to t when the current
+ ;; expressions are on the left side of any assignment, but it's difficult
+ ;; because it requires looking ahead of expression.
+ ((and js2-is-in-destructuring
(= tt js2-NAME)
(let ((ctk (js2-peek-token)))
(or (= ctk js2-COMMA)
(save-excursion
(back-to-indentation)
(or (js2-looking-at-operator-p)
- (when (js2-re-search-backward "\n" nil t) ;; skip comments
- (skip-chars-backward " \t")
- (unless (bolp) ;; previous line is empty
+ (when (catch 'found
+ (while (and (re-search-backward "\n" nil t)
+ (let ((state (syntax-ppss)))
+ (when (nth 4 state)
+ (goto-char (nth 8 state))) ;; skip comments
+ (skip-chars-backward " \t")
+ (if (bolp)
+ t
+ (throw 'found t))))))
+ (backward-char)
+ (when (js2-looking-at-operator-p)
(backward-char)
- (when (js2-looking-at-operator-p)
- (backward-char)
- (not (looking-at "\\*\\|++\\|--\\|/[/*]"))))))))
+ (not (looking-at "\\*\\|++\\|--\\|/[/*]")))))))
(defun js2-end-of-do-while-loop-p ()
"Return non-nil if word after point is `while' of a do-while
(= (current-indentation) saved-indent))))))))
(defun js2-multiline-decl-indentation ()
- "Returns the declaration indentation column if the current line belongs
-to a multiline declaration statement. All declarations are lined up vertically:
-
-var a = 10,
- b = 20,
- c = 30;
-
-Note that if `js2-always-indent-assigned-expr-in-decls-p' is nil, and the first
-assigned expression is a function or array/object literal, it will be indented
-differently:
-
-var o = { var bar = 2,
- foo: 3 o = {
-}, foo: 3
- bar = 2; };
-"
+ "Return the declaration indentation column if the current line belongs
+to a multiline declaration statement. See `js2-pretty-multiline-declarations'."
(let (forward-sexp-function ; use Lisp version
at-opening-bracket)
(save-excursion
(backward-sexp)
(scan-error (setq at-opening-bracket t))))
(when (looking-at js2-declaration-keyword-re)
- (- (1+ (match-end 0)) (point-at-bol)))))))
+ (goto-char (match-end 0))
+ (1+ (current-column)))))))
(defun js2-ctrl-statement-indentation ()
"Return the proper indentation of current line if it is a control statement.
;; so we'll just guess at it.
(if (and (> end (point)) ; not empty literal
(re-search-forward "[^,]]* \\(for\\) " end t)
- ;; not inside a string literal
- (not (nth 3 (parse-partial-sexp bracket (point)))))
+ ;; not inside comment or string literal
+ (let ((state (parse-partial-sexp bracket (point))))
+ (not (or (nth 3 state) (nth 4 state)))))
(match-beginning 1))))))))
(defun js2-array-comp-indentation (parse-status for-kwd)
(let ((ctrl-stmt-indent (js2-ctrl-statement-indentation))
(same-indent-p (looking-at "[]})]\\|\\<case\\>\\|\\<default\\>"))
(continued-expr-p (js2-continued-expression-p))
- (declaration-indent (and js2-pretty-multiline-decl-indentation-p
+ (declaration-indent (and js2-pretty-multiline-declarations
(js2-multiline-decl-indentation)))
(bracket (nth 1 parse-status))
beg)
(cond
;; indent array comprehension continuation lines specially
((and bracket
+ (>= js2-language-version 170)
(not (js2-same-line bracket))
(setq beg (js2-indent-in-array-comp parse-status))
(>= (point) (save-excursion
(goto-char bracket)
(cond
((looking-at "[({[][ \t]*\\(/[/*]\\|$\\)")
- (let ((p (parse-partial-sexp (point-at-bol) (point))))
- (when (save-excursion (skip-chars-backward " \t)")
- (looking-at ")"))
- (backward-list))
- (if (and (nth 1 p)
- (not js2-consistent-level-indent-inner-bracket-p))
- (progn (goto-char (1+ (nth 1 p)))
- (skip-chars-forward " \t"))
- (back-to-indentation)
- (when (and js2-pretty-multiline-decl-indentation-p
- js2-always-indent-assigned-expr-in-decls-p
- (looking-at js2-declaration-keyword-re))
- (goto-char (1+ (match-end 0)))))
- (cond (same-indent-p
- (current-column))
- (continued-expr-p
- (+ (current-column) (* 2 js2-basic-offset)))
- (t
- (+ (current-column) js2-basic-offset)))))
+ (when (save-excursion (skip-chars-backward " \t)")
+ (looking-at ")"))
+ (backward-list))
+ (back-to-indentation)
+ (and (eq js2-pretty-multiline-declarations 'all)
+ (looking-at js2-declaration-keyword-re)
+ (goto-char (1+ (match-end 0))))
+ (cond (same-indent-p
+ (current-column))
+ (continued-expr-p
+ (+ (current-column) (* 2 js2-basic-offset)))
+ (t
+ (+ (current-column) js2-basic-offset))))
(t
(unless same-indent-p
(forward-char)
;; This has to be set before calling parse-partial-sexp below.
(inhibit-point-motion-hooks t))
(setq parse-status (save-excursion
- (syntax-ppss (point-at-bol)))
+ (syntax-ppss (point-at-bol)))
offset (- (point) (save-excursion
- (back-to-indentation)
- (point))))
+ (back-to-indentation)
+ (point))))
(js2-with-underscore-as-word-syntax
(if (nth 4 parse-status)
(js2-lineup-comment parse-status)
(not (js2-1-line-comment-continuation-p)))
(js2-bounce-indent indent-col parse-status bounce-backwards))
;; just indent to the guesser's likely spot
- (t (indent-line-to indent-col)))
- (when (plusp offset)
- (forward-char offset))))))
+ (t (indent-line-to indent-col))))
+ (when (plusp offset)
+ (forward-char offset)))))
(defun js2-indent-region (start end)
"Indent the region, but don't use bounce indenting."
map)
"Keymap used when `js2-minor-mode' is active.")
+;;;###autoload
(define-minor-mode js2-minor-mode
"Minor mode for running js2 as a background linter.
This allows you to use a different major mode for JavaScript editing,
(setq js2-default-externs
(append js2-ecma-262-externs
(if js2-include-browser-externs js2-browser-externs)
- (if js2-include-gears-externs js2-gears-externs)
- (if js2-include-rhino-externs js2-rhino-externs)))
+ (if js2-include-rhino-externs js2-rhino-externs)
+ (if js2-include-node-externs js2-node-externs)))
;; Experiment: make reparse-delay longer for longer files.
(if (plusp js2-dynamic-idle-timer-adjust)
(setq js2-idle-timer-delay
(* js2-idle-timer-delay
(/ (point-max) js2-dynamic-idle-timer-adjust))))
(setq js2-mode-buffer-dirty-p t
- js2-mode-parsing nil
- js2-highlight-level 0) ; no syntax highlighting
+ js2-mode-parsing nil)
+ (set (make-local-variable 'js2-highlight-level) 0) ; no syntax highlighting
(add-hook 'after-change-functions #'js2-minor-mode-edit nil t)
(add-hook 'change-major-mode-hook #'js2-minor-mode-exit nil t)
(js2-reparse))
(js2-remove-overlays)
(setq js2-mode-ast nil))
-(defun js2-display-error-list ()
+(defvar js2-source-buffer nil "Linked source buffer for diagnostics view")
+(make-variable-buffer-local 'js2-source-buffer)
+
+(defun* js2-display-error-list ()
"Display a navigable buffer listing parse errors/warnings."
(interactive)
- (if (not (js2-have-errors-p))
- (message "No errors")
- (let ((srcbuf (current-buffer))
- (errbuf (get-buffer-create "*js-lint*"))
- (errs (js2-errors-and-warnings)))
- (setq errs (sort errs (lambda (e1 e2)
- (funcall '< (second e1) (second e2)))))
+ (unless (js2-have-errors-p)
+ (message "No errors")
+ (return-from js2-display-error-list))
+ (labels ((annotate-list
+ (lst type)
+ "Add diagnostic TYPE and line number to errs list"
+ (mapcar (lambda (err)
+ (list err type (line-number-at-pos (nth 1 err))))
+ lst)))
+ (let* ((srcbuf (current-buffer))
+ (errbuf (get-buffer-create "*js-lint*"))
+ (errors (annotate-list
+ (when js2-mode-ast (js2-ast-root-errors js2-mode-ast))
+ 'js2-error)) ; must be a valid face name
+ (warnings (annotate-list
+ (when js2-mode-ast (js2-ast-root-warnings js2-mode-ast))
+ 'js2-warning)) ; must be a valid face name
+ (all-errs (sort (append errors warnings)
+ (lambda (e1 e2) (< (cadar e1) (cadar e2))))))
(with-current-buffer errbuf
(let ((inhibit-read-only t))
(erase-buffer)
- (dolist (err errs)
- (insert (format "%s\n" err)))
- (pop-to-buffer errbuf))))))
+ (dolist (err all-errs)
+ (destructuring-bind ((msg-key beg end &rest) type line) err
+ (insert-text-button
+ (format "line %d: %s" line (js2-get-msg msg-key))
+ 'face type
+ 'follow-link "\C-m"
+ 'action 'js2-error-buffer-jump
+ 'js2-msg (js2-get-msg msg-key)
+ 'js2-pos beg)
+ (insert "\n"))))
+ (js2-error-buffer-mode)
+ (setq js2-source-buffer srcbuf)
+ (pop-to-buffer errbuf)
+ (goto-char (point-min))
+ (unless (eobp)
+ (js2-error-buffer-view))))))
+
+(defvar js2-error-buffer-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "n" #'js2-error-buffer-next)
+ (define-key map "p" #'js2-error-buffer-prev)
+ (define-key map (kbd "RET") #'js2-error-buffer-jump)
+ (define-key map "o" #'js2-error-buffer-view)
+ (define-key map "q" #'js2-error-buffer-quit)
+ map)
+ "Keymap used for js2 diagnostics buffers.")
+
+(defun js2-error-buffer-mode ()
+ "Major mode for js2 diagnostics buffers.
+Selecting an error will jump it to the corresponding source-buffer error.
+\\{js2-error-buffer-mode-map}"
+ (interactive)
+ (setq major-mode 'js2-error-buffer-mode
+ mode-name "JS Lint Diagnostics")
+ (use-local-map js2-error-buffer-mode-map)
+ (setq truncate-lines t)
+ (set-buffer-modified-p nil)
+ (setq buffer-read-only t)
+ (run-hooks 'js2-error-buffer-mode-hook))
+
+(defun js2-error-buffer-next ()
+ "Move to next error and view it."
+ (interactive)
+ (when (zerop (forward-line 1))
+ (js2-error-buffer-view)))
+
+(defun js2-error-buffer-prev ()
+ "Move to previous error and view it."
+ (interactive)
+ (when (zerop (forward-line -1))
+ (js2-error-buffer-view)))
+
+(defun js2-error-buffer-quit ()
+ "Kill the current buffer."
+ (interactive)
+ (kill-buffer))
+
+(defun js2-error-buffer-jump (&rest ignored)
+ "Jump cursor to current error in source buffer."
+ (interactive)
+ (when (js2-error-buffer-view)
+ (pop-to-buffer js2-source-buffer)))
+
+(defun js2-error-buffer-view ()
+ "Scroll source buffer to show error at current line."
+ (interactive)
+ (cond
+ ((not (eq major-mode 'js2-error-buffer-mode))
+ (message "Not in a js2 errors buffer"))
+ ((not (buffer-live-p js2-source-buffer))
+ (message "Source buffer has been killed"))
+ ((not (wholenump (get-text-property (point) 'js2-pos)))
+ (message "There does not seem to be an error here"))
+ (t
+ (let ((pos (get-text-property (point) 'js2-pos))
+ (msg (get-text-property (point) 'js2-msg)))
+ (save-selected-window
+ (pop-to-buffer js2-source-buffer)
+ (goto-char pos)
+ (message msg))))))
;;;###autoload
(define-derived-mode js2-mode prog-mode "Javascript-IDE"
(setq js2-default-externs
(append js2-ecma-262-externs
(if js2-include-browser-externs js2-browser-externs)
- (if js2-include-gears-externs js2-gears-externs)
- (if js2-include-rhino-externs js2-rhino-externs)))
+ (if js2-include-rhino-externs js2-rhino-externs)
+ (if js2-include-node-externs js2-node-externs)))
(setq font-lock-defaults '(nil t))
(add-hook 'change-major-mode-hook #'js2-mode-exit nil t)
(add-hook 'after-change-functions #'js2-mode-edit nil t)
(setq imenu-create-index-function #'js2-mode-create-imenu-index)
+ (setq next-error-function #'js2-next-error)
(imenu-add-to-menubar (concat "IM-" mode-name))
(add-to-invisibility-spec '(js2-outline . t))
(set (make-local-variable 'line-move-ignore-invisible) t)
(set (make-local-variable 'forward-sexp-function) #'js2-mode-forward-sexp)
- (if (fboundp 'run-mode-hooks)
- (run-mode-hooks 'js2-mode-hook)
- (run-hooks 'js2-mode-hook))
-
(setq js2-mode-functions-hidden nil
js2-mode-comments-hidden nil
js2-mode-buffer-dirty-p t
(if js2-mode-parse-timer
(cancel-timer js2-mode-parse-timer))
(setq js2-mode-parsing nil)
- (setq js2-mode-parse-timer
- (run-with-idle-timer js2-idle-timer-delay nil
- #'js2-mode-idle-reparse (current-buffer))))
+ (let ((timer (timer-create)))
+ (setq js2-mode-parse-timer timer)
+ (timer-set-function timer 'js2-mode-idle-reparse (list (current-buffer)))
+ (timer-set-idle-time timer js2-idle-timer-delay)
+ ;; http://debbugs.gnu.org/cgi/bugreport.cgi?bug=12326
+ (timer-activate-when-idle timer nil)))
(defun js2-mode-idle-reparse (buffer)
"Run `js2-reparse' if BUFFER is the current buffer, or schedule
(js2-time
(setq interrupted-p
(catch 'interrupted
- (setq js2-mode-ast (js2-parse))
+ (js2-parse)
;; if parsing is interrupted, comments and regex
;; literals stay ignored by `parse-partial-sexp'
(remove-text-properties (point-min) (point-max)
(unless interrupted-p
(setq js2-mode-parse-timer nil))))))
-(defun js2-mode-show-node ()
+(defun js2-mode-show-node (event)
"Debugging aid: highlight selected AST node on mouse click."
- (interactive)
- (let ((node (js2-node-at-point))
- beg end)
- (when js2-mode-show-overlay
+ (interactive "e")
+ (mouse-set-point event)
+ (setq deactivate-mark t)
+ (when js2-mode-show-overlay
+ (let ((node (js2-node-at-point))
+ beg end)
(if (null node)
(message "No node found at location %s" (point))
(setq beg (js2-node-abs-pos node)
(defun js2-mode-show-warn-or-err (e face)
"Highlight a warning or error E with FACE.
-E is a list of ((MSG-KEY MSG-ARG) BEG END)."
+E is a list of ((MSG-KEY MSG-ARG) BEG LEN OVERRIDE-FACE).
+The last element is optional. When present, use instead of FACE."
(let* ((key (first e))
(beg (second e))
(end (+ beg (third e)))
(end (max (point-min) (min end (point-max))))
(js2-highlight-level 3) ; so js2-set-face is sure to fire
(ovl (make-overlay beg end)))
- (overlay-put ovl 'font-lock-face face)
+ (overlay-put ovl 'font-lock-face (or (fourth e) face))
(overlay-put ovl 'js2-error t)
(put-text-property beg end 'help-echo (js2-get-msg key))
(put-text-property beg end 'point-entered #'js2-echo-error)))
"Highlight syntax errors."
(when js2-mode-show-parse-errors
(dolist (e (js2-ast-root-errors js2-mode-ast))
- (js2-mode-show-warn-or-err e 'js2-error-face))))
+ (js2-mode-show-warn-or-err e 'js2-error))))
(defun js2-mode-remove-suppressed-warnings ()
"Take suppressed warnings out of the AST warnings list.
"Highlight strict-mode warnings."
(when js2-mode-show-strict-warnings
(dolist (e (js2-ast-root-warnings js2-mode-ast))
- (js2-mode-show-warn-or-err e 'js2-warning-face))))
+ (js2-mode-show-warn-or-err e 'js2-warning))))
(defun js2-echo-error (old-point new-point)
"Called by point-motion hooks."
(let ((msg (get-text-property new-point 'help-echo)))
- (if (and msg (or (not (current-message))
- (string= (current-message) "Quit")))
- (message msg))))
+ (when (and (stringp msg) (or (not (current-message))
+ (string= (current-message) "Quit")))
+ (message msg))))
(defalias #'js2-echo-help #'js2-echo-error)
(defun js2-line-break (&optional soft)
- "Break line at point."
+ "Break line at point and indent, continuing comment if within one.
+If inside a string, and `js2-concat-multiline-strings' is not
+nil, turn it into concatenation."
+ (interactive)
(let ((parse-status (syntax-ppss)))
(cond
;; Check if we're inside a string.
((nth 3 parse-status)
- (js2-mode-split-string parse-status))
+ (if js2-concat-multiline-strings
+ (js2-mode-split-string parse-status)
+ (insert "\n")))
;; Check if inside a block comment.
((nth 4 parse-status)
- (js2-mode-extend-comment))
+ (js2-mode-extend-comment (nth 8 parse-status)))
(t
- (newline)))))
+ (newline-and-indent)))))
(defun js2-mode-split-string (parse-status)
"Turn a newline in mid-string into a string concatenation.
PARSE-STATUS is as documented in `parse-partial-sexp'."
(let* ((col (current-column))
(quote-char (nth 3 parse-status))
- (quote-string (string quote-char))
(string-beg (nth 8 parse-status))
- (indent (or
- (save-excursion
- (back-to-indentation)
- (if (looking-at "\\+")
- (current-column)))
- (save-excursion
- (goto-char string-beg)
- (if (looking-back "\\+\\s-+")
- (goto-char (match-beginning 0)))
- (current-column)))))
- (insert quote-char "\n")
- (indent-to indent)
- (insert "+ " quote-string)
+ (at-eol (eq js2-concat-multiline-strings 'eol)))
+ (insert quote-char)
+ (if at-eol
+ (insert " +\n")
+ (insert "\n"))
+ (unless at-eol
+ (insert "+ "))
+ (js2-indent-line)
+ (insert quote-char)
(when (eolp)
- (insert quote-string)
+ (insert quote-char)
(backward-char 1))))
-(defun js2-mode-extend-comment ()
+(defun js2-mode-extend-comment (start-pos)
"Indent the line and, when inside a comment block, add comment prefix."
(let (star single col first-line needs-close)
(save-excursion
(back-to-indentation)
+ (when (< (point) start-pos)
+ (goto-char start-pos))
(cond
((looking-at "\\*[^/]")
(setq star t
(save-excursion
(skip-chars-forward " \t\r\n")
(not (eq (char-after) ?*))))))
+ (delete-horizontal-space)
(insert "\n")
(cond
(star
(and (zerop (forward-line 1))
(looking-at "\\s-*//"))))
(indent-to col)
- (insert "// "))
- ;; don't need to extend the comment after all
- (js2-enter-indents-newline
- (js2-indent-line)))))
+ (insert "// ")))
+ ;; Don't need to extend the comment after all.
+ (js2-indent-line)))
(defun js2-beginning-of-line ()
- "Toggles point between bol and first non-whitespace char in line.
+ "Toggle point between bol and first non-whitespace char in line.
Also moves past comment delimiters when inside comments."
(interactive)
(let (node beg)
(goto-char (point-at-bol))))))
(defun js2-end-of-line ()
- "Toggles point between eol and last non-whitespace char in line."
+ "Toggle point between eol and last non-whitespace char in line."
(interactive)
(if (eolp)
(skip-chars-backward " \t")
move backward across N balanced expressions."
(interactive "p")
(setq arg (or arg 1))
- (let (node end (start (point)))
+ (save-restriction
+ (widen) ;; `blink-matching-open' calls `narrow-to-region'
+ (js2-reparse))
+ (let ((scan-msg "Containing expression ends prematurely")
+ node (start (point)) pos lp rp child)
(cond
;; backward-sexp
;; could probably make this better for some cases:
(dotimes (i (- arg))
(js2-backward-sws)
(forward-char -1) ; enter the node we backed up to
- (setq node (js2-node-at-point (point) t))
- (goto-char (if node
- (js2-node-abs-pos node)
- (point-min)))))
- (t
- ;; forward-sexp
- (js2-forward-sws)
- (dotimes (i arg)
- (js2-forward-sws)
- (setq node (js2-node-at-point (point) t)
- end (if node (+ (js2-node-abs-pos node)
- (js2-node-len node))))
- (goto-char (or end (point-max))))))))
+ (when (setq node (js2-node-at-point (point) t))
+ (setq pos (js2-node-abs-pos node))
+ (let ((parens (js2-mode-forward-sexp-parens node pos)))
+ (setq lp (car parens)
+ rp (cdr parens))))
+ (goto-char
+ (or (when (and lp (> start lp))
+ (if (and rp (<= start rp))
+ (if (setq child (js2-node-closest-child node (point) lp t))
+ (js2-node-abs-pos child)
+ (goto-char start)
+ (signal 'scan-error (list scan-msg lp lp)))
+ lp))
+ pos
+ (point-min)))))
+ (t
+ ;; forward-sexp
+ (js2-forward-sws)
+ (dotimes (i arg)
+ (js2-forward-sws)
+ (when (setq node (js2-node-at-point (point) t))
+ (setq pos (js2-node-abs-pos node))
+ (let ((parens (js2-mode-forward-sexp-parens node pos)))
+ (setq lp (car parens)
+ rp (cdr parens))))
+ (goto-char
+ (or (when (and rp (<= start rp))
+ (if (> start lp)
+ (if (setq child (js2-node-closest-child node (point) rp))
+ (js2-node-abs-end child)
+ (goto-char start)
+ (signal 'scan-error (list scan-msg rp (1+ rp))))
+ (1+ rp)))
+ (and pos
+ (+ pos
+ (js2-node-len
+ (if (js2-expr-stmt-node-p (js2-node-parent node))
+ ;; stop after the semicolon
+ (js2-node-parent node)
+ node))))
+ (point-max))))))))
+
+(defun js2-mode-forward-sexp-parens (node abs-pos)
+ (cond
+ ((or (js2-array-node-p node)
+ (js2-object-node-p node)
+ (js2-array-comp-node-p node)
+ (memq (aref node 0) '(cl-struct-js2-block-node cl-struct-js2-scope)))
+ (cons abs-pos (+ abs-pos (js2-node-len node) -1)))
+ ((js2-paren-expr-node-p node)
+ (let ((lp (js2-node-lp node))
+ (rp (js2-node-rp node)))
+ (cons (when lp (+ abs-pos lp))
+ (when rp (+ abs-pos rp)))))))
+
+(defun js2-node-closest-child (parent point limit &optional before)
+ (let* ((parent-pos (js2-node-abs-pos parent))
+ (rpoint (- point parent-pos))
+ (rlimit (- limit parent-pos))
+ (min (min rpoint rlimit))
+ (max (max rpoint rlimit))
+ found)
+ (catch 'done
+ (js2-visit-ast
+ parent
+ (lambda (node end-p)
+ (if (eq node parent)
+ t
+ (let ((pos (js2-node-pos node)) ;; Both relative values.
+ (end (+ (js2-node-pos node) (js2-node-len node))))
+ (when (and (>= pos min) (<= end max)
+ (if before (< pos rpoint) (> end rpoint)))
+ (setq found node))
+ (when (> end rpoint)
+ (throw 'done nil)))
+ nil))))
+ found))
(defun js2-errors ()
"Return a list of errors found."
(or (js2-errors) (js2-warnings)))
(defun js2-errors-and-warnings ()
- "Return a copy of the concatenated errors and warnings lists."
- (and js2-mode-ast
- (append (js2-ast-root-errors js2-mode-ast)
- (copy-sequence (js2-ast-root-warnings js2-mode-ast)))))
+ "Return a copy of the concatenated errors and warnings lists.
+They are appended: first the errors, then the warnings.
+Entries are of the form (MSG BEG END)."
+ (when js2-mode-ast
+ (append (js2-ast-root-errors js2-mode-ast)
+ (copy-sequence (js2-ast-root-warnings js2-mode-ast)))))
(defun js2-next-error (&optional arg reset)
"Move to next parse error.