branch: externals/js2-mode commit b3841a7a304d9d1328fdb0868fbbecf0c2f9831f Merge: 999c0e7 8841175 Author: Dmitry Gutov <dgu...@yandex.ru> Commit: GitHub <nore...@github.com>
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)