branch: externals/js2-mode
commit b3841a7a304d9d1328fdb0868fbbecf0c2f9831f
Merge: 999c0e7 8841175
Author: Dmitry Gutov <[email protected]>
Commit: GitHub <[email protected]>
Merge pull request #533 from redguardtoo/master
support optional chaining operator
---
NEWS.md | 1 +
js2-mode.el | 36 +++++++++++++++++++++++++++++++++---
tests/parser.el | 21 +++++++++++++++++++++
3 files changed, 55 insertions(+), 3 deletions(-)
diff --git a/NEWS.md b/NEWS.md
index b3163a4..7fc9a6e 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -7,6 +7,7 @@
`js-mode` with `js2-minor-mode` (see README), rather than
`js2-jsx-mode`.
* Using `js2-jsx-mode` will now trigger a warning in Emacs 27.
+* Support for optional-chaining operator `?.`
## 2019-02-19
diff --git a/js2-mode.el b/js2-mode.el
index 4686be8..c05be21 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -635,7 +635,8 @@ which doesn't seem particularly useful, but Rhino permits
it."
(defvar js2-AWAIT 169) ; await (pseudo keyword)
(defvar js2-HOOK 170) ; conditional (?:)
-(defvar js2-EXPON 171)
+(defvar js2-OPTIONAL-CHAINING 171) ; optional chaining (?.prop obj?.[expr]
func?.())
+(defvar js2-EXPON 172)
(defconst js2-num-tokens (1+ js2-EXPON))
@@ -1656,6 +1657,9 @@ the correct number of ARGS must be provided."
(js2-msg "msg.no.colon.cond"
"missing : in conditional expression")
+(js2-msg "msg.bad.optional.chaining"
+ "missing property name or [ or ( after optional chaining operator")
+
(js2-msg "msg.no.paren.arg"
"missing ) after argument list")
@@ -6074,7 +6078,9 @@ its relevant fields and puts it into `js2-ti-tokens'."
(?,
(throw 'return js2-COMMA))
(??
- (throw 'return js2-HOOK))
+ (if (js2-match-char ?.)
+ (throw 'return js2-OPTIONAL-CHAINING)
+ (throw 'return js2-HOOK)))
(?:
(if (js2-match-char ?:)
js2-COLONCOLON
@@ -10276,6 +10282,24 @@ Returns the list in reverse order. Consumes the
right-paren token."
(setf (js2-node-len pn) (- end beg))) ; end outer if
(js2-parse-member-expr-tail allow-call-syntax pn)))
+(defun js2-parse-optional-chaining-operator (allow-call-syntax pn)
+ (let ((tt (js2-peek-token)))
+ (cond
+ ((eq tt js2-NAME)
+ (setq pn (js2-parse-property-access js2-DOT pn)))
+ ((eq tt js2-LB)
+ ;; skip left bracket token
+ (js2-get-token)
+ (setq pn (js2-parse-element-get pn)))
+ ((and (eq tt js2-LP) allow-call-syntax)
+ ;; unget optional chaining operator
+ ;; so current token is function name and could be highlighted
+ (js2-unget-token)
+ (setq pn (js2-parse-function-call pn t)))
+ (t
+ (js2-report-error "msg.bad.optional.chaining")))
+ pn))
+
(defun js2-parse-member-expr-tail (allow-call-syntax pn)
"Parse a chain of property/array accesses or function calls.
Includes parsing for E4X operators like `..' and `.@'.
@@ -10288,6 +10312,9 @@ Returns an expression tree that includes PN, the parent
node."
(cond
((or (= tt js2-DOT) (= tt js2-DOTDOT))
(setq pn (js2-parse-property-access tt pn)))
+ ((= tt js2-OPTIONAL-CHAINING)
+ (setq pn (js2-parse-optional-chaining-operator allow-call-syntax pn))
+ (unless pn (setq continue nil)))
((= tt js2-DOTQUERY)
(setq pn (js2-parse-dot-query pn)))
((= tt js2-LB)
@@ -10365,11 +10392,14 @@ Last token parsed must be `js2-RB'."
(when (eq (js2-token-type token) js2-NAME)
(js2-record-face 'js2-function-call token)))
-(defun js2-parse-function-call (pn)
+(defun js2-parse-function-call (pn &optional use-optional-chaining-p)
(js2-highlight-function-call (js2-current-token))
(js2-get-token)
(let (args
(pos (js2-node-pos pn)))
+ (when use-optional-chaining-p
+ ;; skip optional chaining operator
+ (js2-get-token))
(setq pn (make-js2-call-node :pos pos
:target pn
:lp (- (js2-current-token-beg) pos)))
diff --git a/tests/parser.el b/tests/parser.el
index 88d3dab..c87fa5f 100644
--- a/tests/parser.el
+++ b/tests/parser.el
@@ -990,6 +990,27 @@ the test."
(js2-deftest-parse exponentiation-prohibits-unary-op
"var a = -b ** c" :syntax-error "-b")
+(js2-deftest optional-chaining-operator-on-property-access
+ "var a = {}; a?.b;"
+ (js2-mode--and-parse)
+ (let ((node (js2-find-node js2-mode-ast 'js2-name-node-p)))
+ (should node)
+ (should (string= (js2-node-text node) "b"))))
+
+(js2-deftest optional-chaining-operator-on-get-element
+ "var a = []; a?.[99];"
+ (js2-mode--and-parse)
+ (let ((node (js2-find-node js2-mode-ast 'js2-number-node-p)))
+ (should node)
+ (should (string= (js2-node-text node) "99"))))
+
+(js2-deftest optional-chaining-operator-on-functioncall
+ "var a = function(b){}; a?.(99);"
+ (js2-mode--and-parse)
+ (let ((node (js2-find-node js2-mode-ast 'js2-number-node-p)))
+ (should node)
+ (should (string= (js2-node-text node) "99"))))
+
(js2-deftest unary-void-node-start
"var c = void 0"
(js2-mode--and-parse)