(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
: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)
+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-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)
-
-(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 'boolean
:group 'js2-mode)
+(defcustom js2-concat-multiline-strings t
+ "Non-nil to automatically turn a newline in mid-string into a
+string concatenation. When `eol', the '+' will be inserted at the
+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-squeeze-spaces t
"Non-nil to normalize whitespace when filling in comments.
Multiple runs of spaces are converted to a single space."
: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)
(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.
"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
- '((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")
(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"))
(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)
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
(member name js2-default-externs)
(member name js2-additional-externs)
(js2-get-defining-scope scope name))
- (js2-set-face pos end 'js2-external-variable 'record)
- (js2-record-text-property pos end 'help-echo "Undeclared variable")
- (js2-record-text-property pos end 'point-entered #'js2-echo-help))))
+ (js2-report-warning "msg.undeclared.variable" name pos (- end pos)
+ 'js2-external-variable))))
(setq js2-recorded-identifiers nil)))
;;; IMenu support
(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)
(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
(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.
(setf (js2-node-len root) (- end pos))
;; 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)
(push param params))
- ;; simple name
+ ;; variable name
(t
+ (when (and (>= js2-language-version 200)
+ (js2-match-token js2-TRIPLEDOT)
+ (not rest-param-at))
+ ;; to report errors if there are more parameters
+ (setq rest-param-at (length params)))
(js2-must-match js2-NAME "msg.no.parm")
(js2-record-face 'js2-function-param)
(setq param (js2-create-name-node))
(js2-define-symbol js2-LP js2-ts-string param)
+ ;; default parameter value
+ (when (or (and default-found
+ (not rest-param-at)
+ (js2-must-match js2-ASSIGN
+ "msg.no.default.after.default.param"
+ (js2-node-pos param)
+ (js2-node-len param)))
+ (and (>= js2-language-version 200)
+ (js2-match-token js2-ASSIGN)))
+ (let* ((pos (js2-node-pos param))
+ (tt js2-current-token)
+ (op-pos (- js2-token-beg pos))
+ (left param)
+ (right (js2-parse-assign-expr))
+ (len (- (js2-node-end right) pos)))
+ (setq param (make-js2-assign-node
+ :type tt :pos pos :len len :op-pos op-pos
+ :left left :right right)
+ default-found t)
+ (js2-node-add-children param left right)))
(push param params)))
+ (when (and rest-param-at (> (length params) (1+ rest-param-at)))
+ (js2-report-error "msg.param.after.rest" nil
+ (js2-node-pos param) (js2-node-len param)))
while
(js2-match-token js2-COMMA))
- (if (js2-must-match js2-RP "msg.no.paren.after.parms")
- (setf (js2-function-node-rp fn-node) (- js2-token-beg pos)))
+ (when (js2-must-match js2-RP "msg.no.paren.after.parms")
+ (setf (js2-function-node-rp fn-node) (- js2-token-beg pos)))
+ (when rest-param-at
+ (setf (js2-function-node-rest-p fn-node) t))
(dolist (p params)
(js2-node-add-children fn-node p)
(push p (js2-function-node-params fn-node))))))
"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)
(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; };
-"
+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)
(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,
(* 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)
+ (append err (list 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) (< (nth 1 e1) (nth 1 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 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"
(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
(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)
+ (interactive "e")
+ (mouse-set-point event)
(let ((node (js2-node-at-point))
beg end)
(when js2-mode-show-overlay
(js2-node-short-name (js2-node-parent node))
"nil"))))))
+(put 'js2-mode-show-node 'CUA 'move)
+
(defun js2-mode-hide-overlay (&optional p1 p2)
"Remove the debugging overlay when the point moves.
P1 and P2 are the old and new values of point, respectively."
(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)))
(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
(looking-at "\\s-*//"))))
(indent-to col)
(insert "// "))
- ;; don't need to extend the comment after all
- (js2-enter-indents-newline
- (js2-indent-line)))))
+ ;; 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.
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.