;; Dmitry Gutov <dgutov@yandex.ru>
;; URL: https://github.com/mooz/js2-mode/
;; http://code.google.com/p/js2-mode/
-;; Version: 20140114
+;; Version: 20141115
;; Keywords: languages, javascript
;; Package-Requires: ((emacs "24.1"))
:type 'hook
:group 'js2-mode)
+(defcustom js2-build-imenu-callbacks nil
+ "List of functions called during Imenu index generation.
+It's a good place to add additional entries to it, using
+`js2-record-imenu-entry'."
+ :type 'hook
+ :group 'js2-mode)
+
(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
"missing ) in parenthetical")
(js2-msg "msg.reserved.id"
- "identifier is a reserved word")
+ "'%s' is a reserved identifier")
(js2-msg "msg.no.paren.catch"
"missing ( before catch-block condition")
"illegal octal literal digit %s; "
"interpreting it as a decimal digit")
-(js2-msg "msg.reserved.keyword"
- "illegal usage of future reserved keyword %s; "
- "interpreting it as ordinary identifier")
+(js2-msg "msg.missing.hex.digits"
+ "missing hexadecimal digits after '0x'")
+
+(js2-msg "msg.missing.binary.digits"
+ "missing binary digits after '0b'")
+
+(js2-msg "msg.missing.octal.digits"
+ "missing octal digits after '0o'")
(js2-msg "msg.script.is.not.constructor"
"Script objects are not constructors.")
,@body)
(modify-syntax-entry ?_ ,old-syntax js2-mode-syntax-table)))))
-(defsubst js2-char-uppercase-p (c)
- "Return t if C is an uppercase character.
-Handles unicode and latin chars properly."
- (/= c (downcase c)))
-
-(defsubst js2-char-lowercase-p (c)
- "Return t if C is an uppercase character.
-Handles unicode and latin chars properly."
- (/= c (upcase c)))
-
;;; AST struct and function definitions
;; flags for ast node property 'member-type (used for e4x operators)
rp ; position of arg-list close-paren, or nil if omitted
ignore-dynamic ; ignore value of the dynamic-scope flag (interpreter only)
needs-activation ; t if we need an activation object for this frame
- generator-type ; STAR, LEGACY or nil
+ generator-type ; STAR, LEGACY, COMPREHENSION or nil
member-expr) ; nonstandard Ecma extension from Rhino
(put 'cl-struct-js2-function-node 'js2-visitor 'js2-visit-function-node)
(defun js2-print-let-node (n i)
(insert (js2-make-pad i) "let (")
- (js2-print-ast (js2-let-node-vars n) 0)
+ (let ((p (point)))
+ (js2-print-ast (js2-let-node-vars n) 0)
+ (delete-region p (+ p 4)))
(insert ") ")
(js2-print-ast (js2-let-node-body n) i))
(defun js2-print-array-node (n i)
(insert (js2-make-pad i) "[")
- (js2-print-list (js2-array-node-elems n))
+ (let ((elems (js2-array-node-elems n)))
+ (js2-print-list elems)
+ (when (and elems (null (car (last elems))))
+ (insert ",")))
(insert "]"))
(defstruct (js2-object-node
(:constructor nil)
(:constructor make-js2-yield-node (&key (type js2-YIELD)
(pos js2-ts-cursor)
- len value)))
+ len value star-p)))
"AST node for yield statement or expression."
+ star-p ; whether it's yield*
value) ; optional: value to be yielded
(put 'cl-struct-js2-yield-node 'js2-visitor 'js2-visit-yield-node)
(defun js2-print-yield-node (n i)
(insert (js2-make-pad i))
(insert "yield")
+ (when (js2-yield-node-star-p n)
+ (insert "*"))
(when (js2-yield-node-value n)
(insert " ")
(js2-print-ast (js2-yield-node-value n) 0)))
(js2-print-ast (js2-paren-node-expr n) 0)
(insert ")"))
-(defstruct (js2-array-comp-node
+(defstruct (js2-comp-node
(:include js2-scope)
(:constructor nil)
- (:constructor make-js2-array-comp-node (&key (type js2-ARRAYCOMP)
- (pos js2-ts-cursor)
- len result
- loops filter
- if-pos lp rp)))
+ (:constructor make-js2-comp-node (&key (type js2-ARRAYCOMP)
+ (pos js2-ts-cursor)
+ len result
+ loops filters
+ form)))
"AST node for an Array comprehension such as [[x,y] for (x in foo) for (y in bar)]."
result ; result expression (just after left-bracket)
- loops ; a Lisp list of `js2-array-comp-loop-node'
- filter ; guard/filter expression
- if-pos ; buffer pos of 'if' keyword, if present, else nil
- lp ; buffer position of if-guard left-paren, or nil if not present
- rp) ; buffer position of if-guard right-paren, or nil if not present
-
-(put 'cl-struct-js2-array-comp-node 'js2-visitor 'js2-visit-array-comp-node)
-(put 'cl-struct-js2-array-comp-node 'js2-printer 'js2-print-array-comp-node)
-
-(defun js2-visit-array-comp-node (n v)
- (js2-visit-ast (js2-array-comp-node-result n) v)
- (dolist (l (js2-array-comp-node-loops n))
+ loops ; a Lisp list of `js2-comp-loop-node'
+ filters ; a Lisp list of guard/filter expressions
+ form ; ARRAY, LEGACY_ARRAY or STAR_GENERATOR
+ ; SpiderMonkey also supports "legacy generator expressions", but we dont.
+ )
+
+(put 'cl-struct-js2-comp-node 'js2-visitor 'js2-visit-comp-node)
+(put 'cl-struct-js2-comp-node 'js2-printer 'js2-print-comp-node)
+
+(defun js2-visit-comp-node (n v)
+ (js2-visit-ast (js2-comp-node-result n) v)
+ (dolist (l (js2-comp-node-loops n))
(js2-visit-ast l v))
- (js2-visit-ast (js2-array-comp-node-filter n) v))
+ (dolist (f (js2-comp-node-filters n))
+ (js2-visit-ast f v)))
-(defun js2-print-array-comp-node (n i)
+(defun js2-print-comp-node (n i)
(let ((pad (js2-make-pad i))
- (result (js2-array-comp-node-result n))
- (loops (js2-array-comp-node-loops n))
- (filter (js2-array-comp-node-filter n)))
- (insert pad "[")
- (js2-print-ast result 0)
+ (result (js2-comp-node-result n))
+ (loops (js2-comp-node-loops n))
+ (filters (js2-comp-node-filters n))
+ (legacy-p (eq (js2-comp-node-form n) 'LEGACY_ARRAY))
+ (gen-p (eq (js2-comp-node-form n) 'STAR_GENERATOR)))
+ (insert pad (if gen-p "(" "["))
+ (when legacy-p
+ (js2-print-ast result 0))
(dolist (l loops)
- (insert " ")
- (js2-print-ast l 0))
- (when filter
- (insert " if (")
- (js2-print-ast filter 0)
- (insert ")"))
- (insert "]")))
-
-(defstruct (js2-array-comp-loop-node
+ (when legacy-p
+ (insert " "))
+ (js2-print-ast l 0)
+ (unless legacy-p
+ (insert " ")))
+ (dolist (f filters)
+ (when legacy-p
+ (insert " "))
+ (insert "if (")
+ (js2-print-ast f 0)
+ (insert ")")
+ (unless legacy-p
+ (insert " ")))
+ (unless legacy-p
+ (js2-print-ast result 0))
+ (insert (if gen-p ")" "]"))))
+
+(defstruct (js2-comp-loop-node
(:include js2-for-in-node)
(:constructor nil)
- (:constructor make-js2-array-comp-loop-node (&key (type js2-FOR)
- (pos js2-ts-cursor)
- len iterator
- object in-pos
- foreach-p
- each-pos
- forof-p
- lp rp)))
+ (:constructor make-js2-comp-loop-node (&key (type js2-FOR)
+ (pos js2-ts-cursor)
+ len iterator
+ object in-pos
+ foreach-p
+ each-pos
+ forof-p
+ lp rp)))
"AST subtree for each 'for (foo in bar)' loop in an array comprehension.")
-(put 'cl-struct-js2-array-comp-loop-node 'js2-visitor 'js2-visit-array-comp-loop)
-(put 'cl-struct-js2-array-comp-loop-node 'js2-printer 'js2-print-array-comp-loop)
+(put 'cl-struct-js2-comp-loop-node 'js2-visitor 'js2-visit-comp-loop)
+(put 'cl-struct-js2-comp-loop-node 'js2-printer 'js2-print-comp-loop)
-(defun js2-visit-array-comp-loop (n v)
- (js2-visit-ast (js2-array-comp-loop-node-iterator n) v)
- (js2-visit-ast (js2-array-comp-loop-node-object n) v))
+(defun js2-visit-comp-loop (n v)
+ (js2-visit-ast (js2-comp-loop-node-iterator n) v)
+ (js2-visit-ast (js2-comp-loop-node-object n) v))
-(defun js2-print-array-comp-loop (n _i)
+(defun js2-print-comp-loop (n _i)
(insert "for ")
- (when (js2-array-comp-loop-node-foreach-p n) (insert "each "))
+ (when (js2-comp-loop-node-foreach-p n) (insert "each "))
(insert "(")
- (js2-print-ast (js2-array-comp-loop-node-iterator n) 0)
- (insert (if (js2-array-comp-loop-node-forof-p n)
+ (js2-print-ast (js2-comp-loop-node-iterator n) 0)
+ (insert (if (js2-comp-loop-node-forof-p n)
" of " " in "))
- (js2-print-ast (js2-array-comp-loop-node-object n) 0)
+ (js2-print-ast (js2-comp-loop-node-object n) 0)
(insert ")"))
(defstruct (js2-empty-expr-node
;; All because Common Lisp doesn't support multiple inheritance for defstructs.
(defconst js2-paren-expr-nodes
- '(cl-struct-js2-array-comp-loop-node
- cl-struct-js2-array-comp-node
+ '(cl-struct-js2-comp-loop-node
+ cl-struct-js2-comp-node
cl-struct-js2-call-node
cl-struct-js2-catch-node
cl-struct-js2-do-node
(js2-catch-node-lp node))
((js2-let-node-p node)
(js2-let-node-lp node))
- ((js2-array-comp-node-p node)
- (js2-array-comp-node-lp node))
+ ((js2-comp-node-p node)
+ 0)
((js2-with-node-p node)
(js2-with-node-lp node))
((js2-xml-dot-query-node-p node)
(js2-catch-node-rp node))
((js2-let-node-p node)
(js2-let-node-rp node))
- ((js2-array-comp-node-p node)
- (js2-array-comp-node-rp node))
+ ((js2-comp-node-p node)
+ (1- (js2-node-len node)))
((js2-with-node-p node)
(js2-with-node-rp node))
((js2-xml-dot-query-node-p node)
(setq node (js2-node-parent node)))
node))
-(defun js2-mode-find-enclosing-node (beg end)
- "Find script or function fully enclosing BEG and END."
+ (defun js2-mode-find-enclosing-node (beg end)
+ "Find node fully enclosing BEG and END."
(let ((node (js2-node-at-point beg))
pos
(continue t))
(while continue
(if (or (js2-ast-root-p node)
- (and (js2-function-node-p node)
- (<= (setq pos (js2-node-abs-pos node)) beg)
- (>= (+ pos (js2-node-len node)) end)))
+ (and
+ (<= (setq pos (js2-node-abs-pos node)) beg)
+ (>= (+ pos (js2-node-len node)) end)))
(setq continue nil)
(setq node (js2-node-parent node))))
node))
(ignore-errors
(let ((s (buffer-substring-no-properties js2-ts-cursor
(+ 4 js2-ts-cursor))))
- (if (string-match "[[:alnum:]]\\{4\\}" s)
+ (if (string-match "[0-9a-fA-F]\\{4\\}" s)
(read (concat "?\\u" s))))))
(defun js2-match-char (test)
(js2-get-char)
(js2-unget-char)))
-(defun js2-java-identifier-start-p (c)
+(defun js2-identifier-start-p (c)
+ "Is C a valid start to an ES5 Identifier?
+See http://es5.github.io/#x7.6"
(or
(memq c '(?$ ?_))
- (js2-char-uppercase-p c)
- (js2-char-lowercase-p c)))
+ (memq (get-char-code-property c 'general-category)
+ ;; Letters
+ '(Lu Ll Lt Lm Lo Nl))))
-(defun js2-java-identifier-part-p (c)
- "Implementation of java.lang.Character.isJavaIdentifierPart()."
- ;; TODO: make me Unicode-friendly. See comments above.
+(defun js2-identifier-part-p (c)
+ "Is C a valid part of an ES5 Identifier?
+See http://es5.github.io/#x7.6"
(or
- (memq c '(?$ ?_))
- (js2-char-uppercase-p c)
- (js2-char-lowercase-p c)
- (and (>= c ?0) (<= c ?9))))
+ (memq c '(?$ ?_ ?\u200c ?\u200d))
+ (memq (get-char-code-property c 'general-category)
+ '(;; Letters
+ Lu Ll Lt Lm Lo Nl
+ ;; Combining Marks
+ Mn Mc
+ ;; Digits
+ Nd
+ ;; Connector Punctuation
+ Pc))))
(defun js2-alpha-p (c)
(cond ((and (<= ?A c) (<= c ?Z)) t)
(aset table js2-NULL 'font-lock-constant-face)
(aset table js2-TRUE 'font-lock-constant-face)
(aset table js2-FALSE 'font-lock-constant-face)
+ (aset table js2-NOT 'font-lock-negation-char-face)
table)
"Vector whose values are non-nil for tokens that are keywords.
The values are default faces to use for highlighting the keywords.")
-(defconst js2-reserved-words
- '(abstract
- boolean byte
- char class
- double
- enum export extends
- final float
- goto
- implements import int interface
- long
- native
- package private protected public
- short static super synchronized
- throws transient
- volatile))
+;; FIXME: Support strict mode-only future reserved words, after we know
+;; which parts scopes are in strict mode, and which are not.
+(defconst js2-reserved-words '(class export extends import super)
+ "Future reserved keywords in ECMAScript 5.")
(defconst js2-keyword-names
(let ((table (make-hash-table :test 'equal)))
its relevant fields and puts it into `js2-ti-tokens'."
(let (c c1 identifier-start is-unicode-escape-start
contains-escape escape-val str result base
- is-integer quote-char val look-for-slash continue tt
+ quote-char val look-for-slash continue tt
(token (js2-new-token 0)))
(setq
tt
(js2-unget-char)
(setq c ?\\)))
(t
- (when (setq identifier-start (js2-java-identifier-start-p c))
+ (when (setq identifier-start (js2-identifier-start-p c))
(setq js2-ts-string-buffer nil)
(js2-add-to-string c))))
(when identifier-start
(js2-report-scan-error "msg.illegal.character" t)))
(t
(if (or (eq c js2-EOF_CHAR)
- (not (js2-java-identifier-part-p c)))
+ (not (js2-identifier-part-p c)))
(throw 'break nil))
(js2-add-to-string c))))))
(js2-unget-char)
(setf str (js2-collect-string js2-ts-string-buffer)
(js2-token-end token) js2-ts-cursor)
+ ;; FIXME: Invalid in ES5 and ES6, see
+ ;; https://bugzilla.mozilla.org/show_bug.cgi?id=694360
+ ;; Probably should just drop this conditional.
(unless contains-escape
;; OPT we shouldn't have to make a string (object!) to
;; check if it's a keyword.
(memq result '(js2-LET js2-YIELD)))
;; LET and YIELD are tokens only in 1.7 and later
(setq result 'js2-NAME))
- (if (not (eq result 'js2-RESERVED))
- (throw 'return (js2-tt-code result)))
- (js2-report-warning "msg.reserved.keyword" str)))
+ (when (eq result 'js2-RESERVED)
+ (setf (js2-token-string token) str))
+ (throw 'return (js2-tt-code result))))
;; If we want to intern these as Rhino does, just use (intern str)
(setf (js2-token-string token) str)
(throw 'return js2-NAME)) ; end identifier/kwd check
((or (eq c ?x) (eq c ?X))
(setq base 16)
(setq c (js2-get-char)))
+ ((and (or (eq c ?b) (eq c ?B))
+ (>= js2-language-version 200))
+ (setq base 2)
+ (setq c (js2-get-char)))
+ ((and (or (eq c ?o) (eq c ?O))
+ (>= js2-language-version 200))
+ (setq base 8)
+ (setq c (js2-get-char)))
((js2-digit-p c)
- (setq base 8))
+ (setq base 'maybe-8))
(t
(js2-add-to-string ?0))))
- (if (eq base 16)
+ (cond
+ ((eq base 16)
+ (if (> 0 (js2-x-digit-to-int c 0))
+ (js2-report-scan-error "msg.missing.hex.digits")
(while (<= 0 (js2-x-digit-to-int c 0))
(js2-add-to-string c)
- (setq c (js2-get-char)))
+ (setq c (js2-get-char)))))
+ ((eq base 2)
+ (if (not (memq c '(?0 ?1)))
+ (js2-report-scan-error "msg.missing.binary.digits")
+ (while (memq c '(?0 ?1))
+ (js2-add-to-string c)
+ (setq c (js2-get-char)))))
+ ((eq base 8)
+ (if (or (> ?0 c) (< ?7 c))
+ (js2-report-scan-error "msg.missing.octal.digits")
+ (while (and (<= ?0 c) (>= ?7 c))
+ (js2-add-to-string c)
+ (setq c (js2-get-char)))))
+ (t
(while (and (<= ?0 c) (<= c ?9))
;; We permit 08 and 09 as decimal numbers, which
;; makes our behavior a superset of the ECMA
;; numeric grammar. We might not always be so
;; permissive, so we warn about it.
- (when (and (eq base 8) (>= c ?8))
+ (when (and (eq base 'maybe-8) (>= c ?8))
(js2-report-warning "msg.bad.octal.literal"
(if (eq c ?8) "8" "9"))
(setq base 10))
(js2-add-to-string c)
- (setq c (js2-get-char))))
- (setq is-integer t)
+ (setq c (js2-get-char)))
+ (when (eq base 'maybe-8)
+ (setq base 8))))
(when (and (eq base 10) (memq c '(?. ?e ?E)))
- (setq is-integer nil)
(when (eq c ?.)
(loop do
(js2-add-to-string c)
(js2-unget-char)
(let ((str (js2-set-string-from-buffer token)))
(setf (js2-token-number token)
- (if (and (eq base 10) (not is-integer))
- (string-to-number str)
- ;; TODO: call runtime number-parser. Some of it is in
- ;; js2-util.el, but I need to port ScriptRuntime.stringToNumber.
- (string-to-number str))))
+ (js2-string-to-number str base)))
(throw 'return js2-NUMBER))
;; is it a string?
(when (memq c '(?\" ?\'))
(setf (js2-token-type token) tt)
token))
+(defsubst js2-string-to-number (str base)
+ ;; TODO: Maybe port ScriptRuntime.stringToNumber.
+ (condition-case nil
+ (string-to-number str base)
+ (overflow-error -1)))
+
(defun js2-read-regexp (start-tt)
"Called by parser when it gets / or /= in literal context."
(let (c err
(if (js2-alpha-p (js2-peek-char))
(js2-report-scan-error "msg.invalid.re.flag" t
js2-ts-cursor 1))
- (js2-set-string-from-buffer token)
- ;; tell `parse-partial-sexp' to ignore this range of chars
- (js2-record-text-property (js2-current-token-beg)
- (js2-current-token-end) 'syntax-class '(2)))
+ (js2-set-string-from-buffer token))
(js2-collect-string flags)))
(defun js2-get-first-xml-token ()
(let ((parent (js2-node-parent node)))
(or
;; function(){...}();
- (js2-call-node-p parent)
+ (and (js2-call-node-p parent)
+ (eq node (js2-call-node-target parent)))
(and (js2-paren-node-p parent)
;; (function(){...})();
(or (js2-call-node-p (setq parent (js2-node-parent parent)))
'("call" "apply"))
(js2-call-node-p (js2-node-parent parent))))))))
-(defun js2-browse-postprocess-chains (entries)
+(defun js2-browse-postprocess-chains ()
"Modify function-declaration name chains after parsing finishes.
Some of the information is only available after the parse tree is complete.
For instance, processing a nested scope requires a parent function node."
(let (result fn parent-qname p elem)
- (dolist (entry entries)
+ (dolist (entry js2-imenu-recorder)
;; function node goes first
(destructuring-bind (current-fn &rest (&whole chain head &rest)) entry
;; Examine head's defining scope:
(gethash grandparent js2-imenu-function-map 'skip)))
'skip))
(puthash fn parent-qname js2-imenu-function-map))
- (unless (eq parent-qname 'skip)
- ;; prefix parent fn qname to this chain.
+ (if (eq parent-qname 'skip)
+ ;; We don't show it, let's record that fact.
+ (remhash current-fn js2-imenu-function-map)
+ ;; Prepend parent fn qname to this chain.
(let ((qname (append parent-qname chain)))
(puthash current-fn (butlast qname) js2-imenu-function-map)
(push qname result)))))))
- ;; finally replace each node in each chain with its name.
+ ;; Collect chains obtained by third-party code.
+ (let (js2-imenu-recorder)
+ (run-hooks 'js2-build-imenu-callbacks)
+ (dolist (entry js2-imenu-recorder)
+ (push (cdr entry) result)))
+ ;; Finally replace each node in each chain with its name.
(dolist (chain result)
(setq p chain)
(while p
(defun js2-build-imenu-index ()
"Turn `js2-imenu-recorder' into an imenu data structure."
- (unless (eq js2-imenu-recorder 'empty)
- (let* ((chains (js2-browse-postprocess-chains js2-imenu-recorder))
- (result (js2-build-alist-trie chains nil)))
- (js2-flatten-trie result))))
+ (when (eq js2-imenu-recorder 'empty)
+ (setq js2-imenu-recorder nil))
+ (let* ((chains (js2-browse-postprocess-chains))
+ (result (js2-build-alist-trie chains nil)))
+ (js2-flatten-trie result)))
(defun js2-test-print-chains (chains)
"Print a list of qname chains.
(defalias 'js2-next-token 'js2-get-token)
-(defun js2-match-token (match)
+(defun js2-match-token (match &optional dont-unget)
"Get next token and return t if it matches MATCH, a bytecode.
Returns nil and consumes nothing if MATCH is not the next token."
(if (/= (js2-get-token) match)
- (ignore (js2-unget-token))
+ (ignore (unless dont-unget (js2-unget-token)))
t))
(defun js2-match-contextual-kwd (name)
(or (= tt js2-NAME)
(and js2-allow-keywords-as-property-names
(plusp tt)
- (aref js2-kwd-tokens tt))))
+ (or (= tt js2-RESERVED)
+ (aref js2-kwd-tokens tt)))))
(defun js2-match-prop-name ()
"Consume token and return t if next token is a valid property name.
"Match next token to token code TOKEN, or record a syntax error.
MSG-ID is the error message to report if the match fails.
Returns t on match, nil if no match."
- (if (js2-match-token token)
+ (if (js2-match-token token t)
t
(js2-report-error msg-id nil pos len)
+ (js2-unget-token)
+ nil))
+
+(defun js2-must-match-name (msg-id)
+ (if (js2-match-token js2-NAME t)
+ t
+ (if (eq (js2-current-token-type) js2-RESERVED)
+ (js2-report-error "msg.reserved.id" (js2-current-token-string))
+ (js2-report-error msg-id)
+ (js2-unget-token))
nil))
(defsubst js2-inside-function ()
(js2-highlight-undeclared-vars))
root))
-(defun js2-function-parser ()
- (js2-get-token)
- (js2-parse-function-stmt))
-
(defun js2-parse-function-closure-body (fn-node)
"Parse a JavaScript 1.8 function closure body."
(let ((js2-nesting-of-function (1+ js2-nesting-of-function)))
(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-must-match-name "msg.no.parm")
(js2-record-face 'js2-function-param)
(setq param (js2-create-name-node))
(js2-define-symbol js2-LP (js2-current-token-string) param)
(defun js2-parse-function-stmt ()
(let ((pos (js2-current-token-beg))
(star-p (js2-match-token js2-MUL)))
- (js2-must-match js2-NAME "msg.unnamed.function.stmt")
+ (js2-must-match-name "msg.unnamed.function.stmt")
(let ((name (js2-create-name-node t))
pn member-expr)
(cond
(aset parsers js2-DEFAULT #'js2-parse-default-xml-namespace)
(aset parsers js2-DO #'js2-parse-do)
(aset parsers js2-FOR #'js2-parse-for)
- (aset parsers js2-FUNCTION #'js2-function-parser)
+ (aset parsers js2-FUNCTION #'js2-parse-function-stmt)
(aset parsers js2-IF #'js2-parse-if)
(aset parsers js2-LC #'js2-parse-block)
(aset parsers js2-LET #'js2-parse-let-stmt)
(js2-define-destruct-symbols param js2-LET nil))
;; simple name
(t
- (js2-must-match js2-NAME "msg.bad.catchcond")
+ (js2-must-match-name "msg.bad.catchcond")
(setq param (js2-create-name-node))
(js2-define-symbol js2-LET (js2-current-token-string) param))))
;; pattern guard
(= (logand after mask) mask)))
(defun js2-parse-return-or-yield (tt expr-context)
- (let ((pos (js2-current-token-beg))
- (end (js2-current-token-end))
- (before js2-end-flags)
- (inside-function (js2-inside-function))
- e ret name)
+ (let* ((pos (js2-current-token-beg))
+ (end (js2-current-token-end))
+ (before js2-end-flags)
+ (inside-function (js2-inside-function))
+ (gen-type (and inside-function (js2-function-node-generator-type
+ js2-current-script-or-fn)))
+ e ret name yield-star-p)
(unless inside-function
(js2-report-error (if (eq tt js2-RETURN)
"msg.bad.return"
"msg.bad.yield")))
+ (when (and inside-function
+ (eq gen-type 'STAR)
+ (js2-match-token js2-MUL))
+ (setq yield-star-p t))
;; This is ugly, but we don't want to require a semicolon.
(unless (memq (js2-peek-token-or-eol) js2-parse-return-stmt-enders)
(setq e (js2-parse-expr)
(js2-now-all-set before js2-end-flags
(logior js2-end-returns js2-end-returns-value)))
(js2-add-strict-warning "msg.return.inconsistent" nil pos end)))
+ ((eq gen-type 'COMPREHENSION)
+ ;; FIXME: We should probably switch to saving and using lastYieldOffset,
+ ;; like SpiderMonkey does.
+ (js2-report-error "msg.syntax" nil pos 5))
(t
- (unless (js2-inside-function)
- (js2-report-error "msg.bad.yield"))
(setq ret (make-js2-yield-node :pos pos
:len (- end pos)
- :value e))
+ :value e
+ :star-p yield-star-p))
(js2-node-add-children ret e)
(unless expr-context
(setq e ret
end (js2-node-end destructuring))
;; Simple variable name
(js2-unget-token)
- (when (js2-must-match js2-NAME "msg.bad.var")
+ (when (js2-must-match-name "msg.bad.var")
(setq name (js2-create-name-node)
nbeg (js2-current-token-beg)
nend (js2-current-token-end)
(setq vars (js2-parse-variables js2-LET (js2-current-token-beg)))
(if (js2-must-match js2-RP "msg.no.paren.let")
(setf (js2-let-node-rp pn) (- (js2-current-token-beg) pos)))
- (if (and stmt-p (eq (js2-get-token) js2-LC))
+ (if (and stmt-p (js2-match-token js2-LC))
;; let statement
(progn
(setf beg (js2-current-token-beg) ; position stmt at LC
(js2-let-node-body pn) body
(js2-node-type pn) js2-LET))
;; let expression
- (js2-unget-token)
(setf body (js2-parse-expr)
(js2-node-len pn) (- (js2-node-end body) pos)
(js2-let-node-body pn) body))
+ (setf (js2-let-node-vars pn) vars)
(js2-node-add-children pn vars body))
(js2-pop-scope))
pn))
(js2-define-new-symbol decl-type name node))
(t (js2-code-bug)))))
+(defun js2-parse-paren-expr-or-generator-comp ()
+ (let ((px-pos (js2-current-token-beg)))
+ (if (and (>= js2-language-version 200)
+ (js2-match-token js2-FOR))
+ (js2-parse-generator-comp px-pos)
+ (let* ((js2-in-for-init nil)
+ (expr (js2-parse-expr))
+ (pn (make-js2-paren-node :pos px-pos
+ :expr expr
+ :len (- (js2-current-token-end)
+ px-pos))))
+ (js2-node-add-children pn (js2-paren-node-expr pn))
+ (js2-must-match js2-RP "msg.no.paren")
+ pn))))
+
(defun js2-parse-expr (&optional oneshot)
(let* ((pn (js2-parse-assign-expr))
(pos (js2-node-pos pn))
(let ((tt (js2-get-token))
(pos (js2-current-token-beg))
pn left right op-pos
- ts-state recorded-identifiers)
+ ts-state recorded-identifiers parsed-errors)
(if (= tt js2-YIELD)
(js2-parse-return-or-yield tt t)
;; Save the tokenizer state in case we find an arrow function
;; and have to rewind.
(setq ts-state (make-js2-ts-state)
- recorded-identifiers js2-recorded-identifiers)
+ recorded-identifiers js2-recorded-identifiers
+ parsed-errors js2-parsed-errors)
;; not yield - parse assignment expression
(setq pn (js2-parse-cond-expr)
tt (js2-get-token))
((and (= tt js2-ARROW)
(>= js2-language-version 200))
(js2-ts-seek ts-state)
- (setq js2-recorded-identifiers recorded-identifiers)
+ (setq js2-recorded-identifiers recorded-identifiers
+ js2-parsed-errors parsed-errors)
(setq pn (js2-parse-function 'FUNCTION_ARROW (js2-current-token-beg) nil)))
(t
(js2-unget-token)))
c-pos)
(when (js2-match-token js2-HOOK)
(setq q-pos (- (js2-current-token-beg) pos)
- if-true (js2-parse-assign-expr))
+ if-true (let (js2-in-for-init) (js2-parse-assign-expr)))
(js2-must-match js2-COLON "msg.no.colon.cond")
(setq c-pos (- (js2-current-token-beg) pos)
if-false (js2-parse-assign-expr)
Includes complex literals such as functions, object-literals,
array-literals, array comprehensions and regular expressions."
(let (pn ; parent node (usually return value)
- tt
- px-pos ; paren-expr pos
- len
- flags ; regexp flags
- expr)
+ tt)
(setq tt (js2-current-token-type))
(cond
((= tt js2-FUNCTION)
(js2-parse-function-expr))
((= tt js2-LB)
- (js2-parse-array-literal))
+ (js2-parse-array-comp-or-literal))
((= tt js2-LC)
(js2-parse-object-literal))
((= tt js2-LET)
(js2-parse-let (js2-current-token-beg)))
((= tt js2-LP)
- (setq px-pos (js2-current-token-beg)
- expr (js2-parse-expr))
- (js2-must-match js2-RP "msg.no.paren")
- (setq pn (make-js2-paren-node :pos px-pos
- :expr expr
- :len (- (js2-current-token-end) px-pos)))
- (js2-node-add-children pn (js2-paren-node-expr pn))
- pn)
+ (js2-parse-paren-expr-or-generator-comp))
((= tt js2-XMLATTR)
(js2-must-have-xml)
(js2-parse-attribute-access))
(js2-record-face 'font-lock-string-face)))
((or (= tt js2-DIV) (= tt js2-ASSIGN_DIV))
;; Got / or /= which in this context means a regexp literal
- (setq px-pos (js2-current-token-beg))
- (setq flags (js2-read-regexp tt))
- (prog1
- (make-js2-regexp-node :pos px-pos
- :len (- js2-ts-cursor px-pos)
- :value (js2-current-token-string)
- :flags flags)
- (js2-set-face px-pos js2-ts-cursor 'font-lock-string-face 'record)
- (js2-record-text-property px-pos js2-ts-cursor 'syntax-table '(2))))
+ (let ((px-pos (js2-current-token-beg))
+ (flags (js2-read-regexp tt))
+ (end (js2-current-token-end)))
+ (prog1
+ (make-js2-regexp-node :pos px-pos
+ :len (- end px-pos)
+ :value (js2-current-token-string)
+ :flags flags)
+ (js2-set-face px-pos end 'font-lock-string-face 'record)
+ (js2-record-text-property px-pos end 'syntax-table '(2)))))
((or (= tt js2-NULL)
(= tt js2-THIS)
(= tt js2-FALSE)
;; the scanner or one of its subroutines reported the error.
(make-js2-error-node))
((= tt js2-EOF)
- (setq px-pos (point-at-bol)
- len (- js2-ts-cursor px-pos))
- (js2-report-error "msg.unexpected.eof" nil px-pos len)
+ (let* ((px-pos (point-at-bol))
+ (len (- js2-ts-cursor px-pos)))
+ (js2-report-error "msg.unexpected.eof" nil px-pos len))
(make-js2-error-node :pos (1- js2-ts-cursor)))
(t
(js2-report-error "msg.syntax")
(point)))
comma-pos))
-(defun js2-parse-array-literal ()
- (let ((pos (js2-current-token-beg))
- (after-lb-or-comma t)
+(defun js2-parse-array-comp-or-literal ()
+ (let ((pos (js2-current-token-beg)))
+ (if (and (>= js2-language-version 200)
+ (js2-match-token js2-FOR))
+ (js2-parse-array-comp pos)
+ (js2-parse-array-literal pos))))
+
+(defun js2-parse-array-literal (pos)
+ (let ((after-lb-or-comma t)
after-comma tt elems pn
(continue t))
(unless js2-is-in-destructuring
- (js2-push-scope (make-js2-scope))) ; for array comp
+ (js2-push-scope (make-js2-scope))) ; for the legacy array comp
(while continue
(setq tt (js2-get-token))
(cond
(= tt js2-EOF)) ; prevent infinite loop
(if (= tt js2-EOF)
(js2-report-error "msg.no.bracket.arg" nil pos))
+ (when (and after-comma (< js2-language-version 170))
+ (js2-parse-warn-trailing-comma "msg.array.trailing.comma"
+ pos (remove nil elems) after-comma))
(setq continue nil
pn (make-js2-array-node :pos pos
:len (- js2-ts-cursor pos)
:elems (nreverse elems)))
- (apply #'js2-node-add-children pn (js2-array-node-elems pn))
- (when (and after-comma (not js2-is-in-destructuring))
- (js2-parse-warn-trailing-comma "msg.array.trailing.comma"
- pos elems after-comma)))
+ (apply #'js2-node-add-children pn (js2-array-node-elems pn)))
;; destructuring binding
(js2-is-in-destructuring
(push (if (or (= tt js2-LC)
(not (cdr elems))) ; but no 2nd element
(js2-unget-token)
(setf continue nil
- pn (js2-parse-array-comprehension (car elems) pos)))
+ pn (js2-parse-legacy-array-comp (car elems) pos)))
;; another element
(t
(unless after-lb-or-comma
(js2-pop-scope))
pn))
-(defun js2-parse-array-comprehension (expr pos)
- "Parse a JavaScript 1.7 Array Comprehension.
+(defun js2-parse-legacy-array-comp (expr pos)
+ "Parse a legacy array comprehension (JavaScript 1.7).
EXPR is the first expression after the opening left-bracket.
POS is the beginning of the LB token preceding EXPR.
We should have just parsed the 'for' keyword before calling this function."
- (let (loops loop first filter if-pos result)
- (while (= (js2-get-token) js2-FOR)
- (let ((prev (car loops))) ; rearrange scope chain
- (push (setq loop (js2-parse-array-comp-loop)) loops)
- (if prev ; each loop is parent scope to the next one
- (setf (js2-scope-parent-scope loop) prev)
- ; first loop takes expr scope's parent
- (setf (js2-scope-parent-scope (setq first loop))
- (js2-scope-parent-scope js2-current-scope)))))
- (js2-unget-token)
- ;; set expr scope's parent to the last loop
- (setf (js2-scope-parent-scope js2-current-scope) (car loops))
- (if (/= (js2-get-token) js2-IF)
- (js2-unget-token)
- (setq if-pos (- (js2-current-token-beg) pos) ; relative
- filter (js2-parse-condition)))
+ (let ((current-scope js2-current-scope)
+ loops first filter result)
+ (unwind-protect
+ (progn
+ (while (js2-match-token js2-FOR)
+ (let ((loop (make-js2-comp-loop-node)))
+ (js2-push-scope loop)
+ (push loop loops)
+ (js2-parse-comp-loop loop)))
+ ;; First loop takes expr scope's parent.
+ (setf (js2-scope-parent-scope (setq first (car (last loops))))
+ (js2-scope-parent-scope current-scope))
+ ;; Set expr scope's parent to the last loop.
+ (setf (js2-scope-parent-scope current-scope) (car loops))
+ (if (/= (js2-get-token) js2-IF)
+ (js2-unget-token)
+ (setq filter (js2-parse-condition))))
+ (dotimes (_ (1- (length loops)))
+ (js2-pop-scope)))
(js2-must-match js2-RB "msg.no.bracket.arg" pos)
- (setq result (make-js2-array-comp-node :pos pos
- :len (- js2-ts-cursor pos)
- :result expr
- :loops (nreverse loops)
- :filter (car filter)
- :lp (js2-relpos (second filter) pos)
- :rp (js2-relpos (third filter) pos)
- :if-pos if-pos))
+ (setq result (make-js2-comp-node :pos pos
+ :len (- js2-ts-cursor pos)
+ :result expr
+ :loops (nreverse loops)
+ :filters (and filter (list (car filter)))
+ :form 'LEGACY_ARRAY))
(apply #'js2-node-add-children result expr (car filter)
- (js2-array-comp-node-loops result))
- (setq js2-current-scope first) ; pop to the first loop
+ (js2-comp-node-loops result))
result))
-(defun js2-parse-array-comp-loop ()
- "Parse a 'for [each] (foo [in|of] bar)' expression in an Array comprehension.
-Last token peeked should be the initial FOR."
- (let ((pos (js2-current-token-beg))
- (pn (make-js2-array-comp-loop-node))
- tt iter obj foreach-p forof-p in-pos each-pos lp rp)
- (assert (= (js2-current-token-type) js2-FOR))
- (js2-push-scope pn)
+(defun js2-parse-array-comp (pos)
+ "Parse an ES6 array comprehension.
+POS is the beginning of the LB token.
+We should have just parsed the 'for' keyword before calling this function."
+ (let ((pn (js2-parse-comprehension pos 'ARRAY)))
+ (js2-must-match js2-RB "msg.no.bracket.arg" pos)
+ pn))
+
+(defun js2-parse-generator-comp (pos)
+ (let* ((js2-nesting-of-function (1+ js2-nesting-of-function))
+ (js2-current-script-or-fn
+ (make-js2-function-node :generator-type 'COMPREHENSION))
+ (pn (js2-parse-comprehension pos 'STAR_GENERATOR)))
+ (js2-must-match js2-RP "msg.no.paren" pos)
+ pn))
+
+(defun js2-parse-comprehension (pos form)
+ (let (loops filters expr result)
(unwind-protect
(progn
- (when (js2-match-token js2-NAME)
- (if (string= (js2-current-token-string) "each")
- (progn
- (setq foreach-p t
- each-pos (- (js2-current-token-beg) pos)) ; relative
- (js2-record-face 'font-lock-keyword-face))
- (js2-report-error "msg.no.paren.for")))
- (if (js2-must-match js2-LP "msg.no.paren.for")
- (setq lp (- (js2-current-token-beg) pos)))
- (setq tt (js2-peek-token))
- (cond
- ((or (= tt js2-LB)
- (= tt js2-LC))
- (js2-get-token)
- (setq iter (js2-parse-destruct-primary-expr))
- (js2-define-destruct-symbols iter js2-LET
- 'font-lock-variable-name-face t))
- ((js2-match-token js2-NAME)
- (setq iter (js2-create-name-node)))
- (t
- (js2-report-error "msg.bad.var")))
- ;; Define as a let since we want the scope of the variable to
- ;; be restricted to the array comprehension
- (if (js2-name-node-p iter)
- (js2-define-symbol js2-LET (js2-name-node-name iter) pn t))
- (if (or (js2-match-token js2-IN)
- (and (>= js2-language-version 200)
- (js2-match-contextual-kwd "of")
- (setq forof-p t)))
- (setq in-pos (- (js2-current-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-current-token-beg) pos)))
- (setf (js2-node-pos pn) pos
- (js2-node-len pn) (- js2-ts-cursor pos)
- (js2-array-comp-loop-node-iterator pn) iter
- (js2-array-comp-loop-node-object pn) obj
- (js2-array-comp-loop-node-in-pos pn) in-pos
- (js2-array-comp-loop-node-each-pos pn) each-pos
- (js2-array-comp-loop-node-foreach-p pn) foreach-p
- (js2-array-comp-loop-node-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))
- (js2-pop-scope))
+ (js2-unget-token)
+ (while (js2-match-token js2-FOR)
+ (let ((loop (make-js2-comp-loop-node)))
+ (js2-push-scope loop)
+ (push loop loops)
+ (js2-parse-comp-loop loop)))
+ (while (js2-match-token js2-IF)
+ (push (car (js2-parse-condition)) filters))
+ (setq expr (js2-parse-assign-expr)))
+ (dolist (_ loops)
+ (js2-pop-scope)))
+ (setq result (make-js2-comp-node :pos pos
+ :len (- js2-ts-cursor pos)
+ :result expr
+ :loops (nreverse loops)
+ :filters (nreverse filters)
+ :form form))
+ (apply #'js2-node-add-children result (js2-comp-node-loops result))
+ (apply #'js2-node-add-children result expr (js2-comp-node-filters result))
+ result))
+
+(defun js2-parse-comp-loop (pn &optional only-of-p)
+ "Parse a 'for [each] (foo [in|of] bar)' expression in an Array comprehension.
+The current token should be the initial FOR.
+If ONLY-OF-P is non-nil, only the 'for (foo of bar)' form is allowed."
+ (let ((pos (js2-comp-loop-node-pos pn))
+ tt iter obj foreach-p forof-p in-pos each-pos lp rp)
+ (when (and (not only-of-p) (js2-match-token js2-NAME))
+ (if (string= (js2-current-token-string) "each")
+ (progn
+ (setq foreach-p t
+ each-pos (- (js2-current-token-beg) pos)) ; relative
+ (js2-record-face 'font-lock-keyword-face))
+ (js2-report-error "msg.no.paren.for")))
+ (if (js2-must-match js2-LP "msg.no.paren.for")
+ (setq lp (- (js2-current-token-beg) pos)))
+ (setq tt (js2-peek-token))
+ (cond
+ ((or (= tt js2-LB)
+ (= tt js2-LC))
+ (js2-get-token)
+ (setq iter (js2-parse-destruct-primary-expr))
+ (js2-define-destruct-symbols iter js2-LET
+ 'font-lock-variable-name-face t))
+ ((js2-match-token js2-NAME)
+ (setq iter (js2-create-name-node)))
+ (t
+ (js2-report-error "msg.bad.var")))
+ ;; Define as a let since we want the scope of the variable to
+ ;; be restricted to the array comprehension
+ (if (js2-name-node-p iter)
+ (js2-define-symbol js2-LET (js2-name-node-name iter) pn t))
+ (if (or (and (not only-of-p) (js2-match-token js2-IN))
+ (and (>= js2-language-version 200)
+ (js2-match-contextual-kwd "of")
+ (setq forof-p t)))
+ (setq in-pos (- (js2-current-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-current-token-beg) pos)))
+ (setf (js2-node-pos pn) pos
+ (js2-node-len pn) (- js2-ts-cursor pos)
+ (js2-comp-loop-node-iterator pn) iter
+ (js2-comp-loop-node-object pn) obj
+ (js2-comp-loop-node-in-pos pn) in-pos
+ (js2-comp-loop-node-each-pos pn) each-pos
+ (js2-comp-loop-node-foreach-p pn) foreach-p
+ (js2-comp-loop-node-forof-p pn) forof-p
+ (js2-comp-loop-node-lp pn) lp
+ (js2-comp-loop-node-rp pn) rp)
+ (js2-node-add-children pn iter obj)
pn))
(defun js2-parse-object-literal ()
(defun js2-parse-plain-property (prop)
"Parse a non-getter/setter property in an object literal.
PROP is the node representing the property: a number, name or string."
- (js2-must-match js2-COLON "msg.no.colon.prop")
- (let* ((pos (js2-node-pos prop))
- (colon (- (js2-current-token-beg) pos))
- (expr (js2-parse-assign-expr))
- (result (make-js2-object-prop-node
- :pos pos
- ;; don't include last consumed token in length
- :len (- (+ (js2-node-pos expr)
- (js2-node-len expr))
- pos)
- :left prop
- :right expr
- :op-pos colon)))
- (js2-node-add-children result prop expr)
- result))
+ (let ((pos (js2-node-pos prop))
+ colon expr)
+ (if (js2-must-match js2-COLON "msg.no.colon.prop")
+ (setq colon (- (js2-current-token-beg) pos)
+ expr (js2-parse-assign-expr))
+ (setq expr (make-js2-error-node)))
+ (let ((result (make-js2-object-prop-node
+ :pos pos
+ ;; don't include last consumed token in length
+ :len (- (+ (js2-node-pos expr)
+ (js2-node-len expr))
+ pos)
+ :left prop
+ :right expr
+ :op-pos colon)))
+ (js2-node-add-children result prop expr)
+ result)))
(defun js2-parse-getter-setter-prop (pos prop get-p)
"Parse getter or setter property in an object literal.
followed by an opening brace.")
(defconst js2-indent-operator-re
- (concat "[-+*/%<>=&^|?:.]\\([^-+*/]\\|$\\)\\|"
+ (concat "[-+*/%<>&^|?:.]\\([^-+*/]\\|$\\)\\|!?=\\|"
(regexp-opt '("in" "instanceof") 'words))
"Regular expression matching operators that affect indentation
of continued expressions.")
(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
(define-minor-mode js2-minor-mode
"Minor mode for running js2 as a background linter.
This allows you to use a different major mode for JavaScript editing,
-such as `espresso-mode', while retaining the asynchronous error/warning
+such as `js-mode', while retaining the asynchronous error/warning
highlighting features of `js2-mode'."
:group 'js2-mode
:lighter " js-lint"
(put 'js2-mode 'find-tag-default-function #'js2-mode-find-tag)
(set (make-local-variable 'electric-indent-chars)
- (append '("{" "}" "(" ")" "[" "]" ":" ";" "," "*")
- electric-indent-chars))
+ (append "{}()[]:;,*." electric-indent-chars))
(set (make-local-variable 'electric-layout-rules)
'((?\; . after) (?\{ . after) (?\} . before)))
(defun js2-mode-idle-reparse (buffer)
"Run `js2-reparse' if BUFFER is the current buffer, or schedule
it to be reparsed when the buffer is selected."
- (if (eq buffer (current-buffer))
- (js2-reparse)
- ;; reparse when the buffer is selected again
- (with-current-buffer buffer
- (add-hook 'window-configuration-change-hook
- #'js2-mode-idle-reparse-inner
- nil t))))
+ (cond ((eq buffer (current-buffer))
+ (js2-reparse))
+ ((buffer-live-p buffer)
+ ;; reparse when the buffer is selected again
+ (with-current-buffer buffer
+ (add-hook 'window-configuration-change-hook
+ #'js2-mode-idle-reparse-inner
+ nil t)))))
(defun js2-mode-idle-reparse-inner ()
(remove-hook 'window-configuration-change-hook
(while (and (zerop (forward-line direction))
(looking-at js2-mode-//-comment-re)
(eq indent (length (match-string 1))))
- (setq pos (point-at-eol))
- pos))))
+ (setq pos (point-at-eol)))
+ pos)))
(defun js2-mode-hide-//-comments ()
"Fold adjacent 1-line comments, showing only snippet of first one."
(cond
((or (js2-array-node-p node)
(js2-object-node-p node)
- (js2-array-comp-node-p node)
+ (js2-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)