From: Dmitry Gutov Date: Thu, 23 Jun 2016 01:12:33 +0000 (+0300) Subject: Merge commit '0cda39255827f283e7578cd469ae42daad9556a2' from js2-mode X-Git-Url: https://code.delx.au/gnu-emacs-elpa/commitdiff_plain/04f3d4bb44d7ef30e705b53f3d15e3279ac0d230?hp=-c Merge commit '0cda39255827f283e7578cd469ae42daad9556a2' from js2-mode --- 04f3d4bb44d7ef30e705b53f3d15e3279ac0d230 diff --combined packages/js2-mode/Makefile index 08b1e4862,7777a67c3..7777a67c3 --- a/packages/js2-mode/Makefile +++ b/packages/js2-mode/Makefile @@@ -10,17 -10,14 +10,14 @@@ SRCS = js2-mode.el js2-imenu-extras.e OBJS = $(SRCS:.el=.elc) %.elc: %.el - ${EMACS} $(BATCHFLAGS) -f batch-byte-compile $^ + ${EMACS} $(BATCHFLAGS) -L . -f batch-byte-compile $^ all: $(OBJS) clean: -rm -f $(OBJS) - # custom build (require loads) - js2-imenu-extras.elc: js2-mode.elc - ${EMACS} $(BATCHFLAGS) -l ./js2-mode.elc -f batch-byte-compile $*.el - test: ${EMACS} $(BATCHFLAGS) -L . -l js2-mode.el -l js2-old-indent.el -l tests/parser.el\ - -l tests/indent.el -l tests/externs.el -f ert-run-tests-batch-and-exit + -l tests/indent.el -l tests/externs.el -l tests/json-path.el \ + -l tests/navigation.el -f ert-run-tests-batch-and-exit diff --combined packages/js2-mode/NEWS.md index 2984e91ee,87e9d726a..87e9d726a --- a/packages/js2-mode/NEWS.md +++ b/packages/js2-mode/NEWS.md @@@ -1,5 -1,23 +1,23 @@@ # History of user-visible changes + ## 2016-06-23 + + * New variable `js2-mode-assume-strict`, for use with ES6 modules. + * Support for JSDoc @callback, @func and @method tags. + * Object properties are highlighted using a different face: + `js2-object-property`, which has no color by default. + * Experimental support for object rest/spread ECMAScript proposal. + * `js2-getter-setter-node` is renamed to `js2-method-node`, together with + its related functions. It already handles generator methods, and we + added support for async methods (see below), so the old name would get + more confusing. + * Support for default parameters in destructuring. It should work for both + objects and arrays, in both literals and function arguments. + * New mode: `js2-jsx-mode`, deriving from `js2-mode`. Supports indentation of + JSXElement expressions wrapped within parentheses or as function arguments. + Indentation is customizable via `sgml-attribute-offset`. + * Experimental support for async/await ECMAScript proposal. + ## 20150909 * `js2-mode` now derives from `js-mode`. That means the former diff --combined packages/js2-mode/README.md index b0ee44408,cbce9c37c..cbce9c37c --- a/packages/js2-mode/README.md +++ b/packages/js2-mode/README.md @@@ -1,4 -1,4 +1,4 @@@ - About [![Build Status](https://travis-ci.org/mooz/js2-mode.png?branch=master)](https://travis-ci.org/mooz/js2-mode) + About [![Build Status](https://travis-ci.org/mooz/js2-mode.svg?branch=master)](https://travis-ci.org/mooz/js2-mode) [![MELPA](https://melpa.org/packages/js2-mode-badge.svg)](https://melpa.org/#/js2-mode) ====== Improved JavaScript editing mode for GNU Emacs ([description here](http://elpa.gnu.org/packages/js2-mode.html)). @@@ -12,7 -12,7 +12,7 @@@ The stable versions are hosted at [GNU (M-x list-packages). You can also install the latest development version from - [Melpa](http://melpa.milkbox.net/#installing). + [MELPA](https://melpa.org/#/getting-started). Emacs 22 and 23 =============== diff --combined packages/js2-mode/js2-mode.el index 2d6f33685,db6138905..a2e58fafa --- a/packages/js2-mode/js2-mode.el +++ b/packages/js2-mode/js2-mode.el @@@ -1,13 -1,13 +1,13 @@@ ;;; js2-mode.el --- Improved JavaScript editing mode - ;; Copyright (C) 2009, 2011-2015 Free Software Foundation, Inc. + ;; Copyright (C) 2009, 2011-2016 Free Software Foundation, Inc. ;; Author: Steve Yegge ;; mooz ;; Dmitry Gutov ;; URL: https://github.com/mooz/js2-mode/ ;; http://code.google.com/p/js2-mode/ - ;; Version: 20150909 + ;; Version: 20160623 ;; Keywords: languages, javascript ;; Package-Requires: ((emacs "24.1") (cl-lib "0.5")) @@@ -60,6 -60,12 +60,12 @@@ ;; (add-to-list 'interpreter-mode-alist '("node" . js2-mode)) + ;; Support for JSX is available via the derived mode `js2-jsx-mode'. If you + ;; also want JSX support, use that mode instead: + + ;; (add-to-list 'auto-mode-alist '("\\.jsx?\\'" . js2-jsx-mode)) + ;; (add-to-list 'interpreter-mode-alist '("node" . js2-jsx-mode)) + ;; To customize how it works: ;; M-x customize-group RET js2-mode RET @@@ -95,6 -101,7 +101,7 @@@ (require 'js2-old-indent) (defvaralias 'js2-basic-offset 'js-indent-level nil) (defalias 'js2-proper-indentation 'js--proper-indentation) + (defalias 'js2-jsx-indent-line 'js-jsx-indent-line) (defalias 'js2-indent-line 'js-indent-line) (defalias 'js2-re-search-forward 'js--re-search-forward))) @@@ -192,7 -199,7 +199,7 @@@ Set `js2-include-rhino-externs' to t t (mapcar 'symbol-name '(__dirname __filename Buffer clearInterval clearTimeout require console exports global module process setInterval setTimeout - querystring)) + querystring setImmediate clearImmediate)) "Node.js externs. Set `js2-include-node-externs' to t to include them.") @@@ -270,6 -277,11 +277,11 @@@ end of the line, otherwise, at the begi :type 'boolean :group 'js2-mode) + (defcustom js2-mode-assume-strict nil + "Non-nil to start files in strict mode automatically." + :type 'boolean + :group 'js2-mode) + (defcustom js2-mode-show-strict-warnings t "Non-nil to emit Ecma strict-mode warnings. Some of the warnings can be individually disabled by other flags, @@@ -610,7 -622,9 +622,9 @@@ which doesn't seem particularly useful (defvar js2-NO_SUBS_TEMPLATE 167) ; template literal without substitutions (defvar js2-TAGGED_TEMPLATE 168) ; tagged template literal - (defconst js2-num-tokens (1+ js2-TAGGED_TEMPLATE)) + (defvar js2-AWAIT 169) ; await (pseudo keyword) + + (defconst js2-num-tokens (1+ js2-AWAIT)) (defconst js2-debug-print-trees nil) @@@ -668,6 -682,7 +682,7 @@@ List of chars built up while scanning v (string "") number number-base + number-legacy-octal-p regexp-flags comment-type follows-eol-p) @@@ -1018,6 -1033,11 +1033,11 @@@ in large files." "Face used to highlight function name in calls." :group 'js2-mode) + (defface js2-object-property + '((t :inherit default)) + "Face used to highlight named property in object literal." + :group 'js2-mode) + (defface js2-instance-member '((t :foreground "DarkOrchid")) "Face used to highlight instance variables in javascript. @@@ -1061,6 -1081,7 +1081,7 @@@ Not currently used. "Face used to highlight undeclared variable identifiers.") (defcustom js2-init-hook nil + ;; FIXME: We don't really need this anymore. "List of functions to be called after `js2-mode' or `js2-minor-mode' has initialized all variables, before parsing the buffer for the first time." @@@ -1350,6 -1371,9 +1371,9 @@@ the correct number of ARGS must be prov (js2-msg "msg.yield.parenthesized" "yield expression must be parenthesized.") + (js2-msg "msg.bad.await" + "await must be in async functions.") + ;; NativeGlobal (js2-msg "msg.cant.call.indirect" "Function '%s' must be called directly, and not by way of a " @@@ -1437,7 -1461,7 +1461,7 @@@ "Compilation produced %s syntax errors.") (js2-msg "msg.var.redecl" - "TypeError: redeclaration of var %s.") + "Redeclaration of var %s.") (js2-msg "msg.const.redecl" "TypeError: redeclaration of const %s.") @@@ -1712,6 -1736,9 +1736,9 @@@ (js2-msg "msg.destruct.assign.no.init" "Missing = in destructuring declaration") + (js2-msg "msg.init.no.destruct" + "Binding initializer not in destructuring assignment") + (js2-msg "msg.no.octal.strict" "Octal numbers prohibited in strict mode.") @@@ -2530,7 -2557,10 +2557,10 @@@ so many of its properties will be nil (js2-print-from-clause from)) (exports-list (js2-print-named-imports exports-list))) - (insert ";\n"))) + (unless (or (and default (not (js2-assign-node-p default))) + (and declaration (or (js2-function-node-p declaration) + (js2-class-node-p declaration)))) + (insert ";\n")))) (cl-defstruct (js2-while-node (:include js2-loop-node) @@@ -3241,6 -3271,7 +3271,7 @@@ a `js2-label-node' or the innermost enc params rest-p body generator-type + async lp rp))) "AST node for a function declaration. The `params' field is a Lisp list of nodes. Each node is either a simple @@@ -3257,6 -3288,7 +3288,7 @@@ 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, COMPREHENSION or nil + async ; t if the function is defined as `async function` member-expr) ; nonstandard Ecma extension from Rhino (put 'cl-struct-js2-function-node 'js2-visitor 'js2-visit-function-node) @@@ -3270,7 -3302,7 +3302,7 @@@ (defun js2-print-function-node (n i) (let* ((pad (js2-make-pad i)) - (getter (js2-node-get-prop n 'GETTER_SETTER)) + (method (js2-node-get-prop n 'METHOD_TYPE)) (name (or (js2-function-node-name n) (js2-function-node-member-expr n))) (params (js2-function-node-params n)) @@@ -3278,8 -3310,10 +3310,10 @@@ (rest-p (js2-function-node-rest-p n)) (body (js2-function-node-body n)) (expr (not (eq (js2-function-node-form n) 'FUNCTION_STATEMENT)))) - (unless (or getter arrow) - (insert pad "function") + (unless method + (insert pad) + (when (js2-function-node-async n) (insert "async ")) + (unless arrow (insert "function")) (when (eq (js2-function-node-generator-type n) 'STAR) (insert "*"))) (when name @@@ -3450,6 -3484,8 +3484,8 @@@ The type field inherited from `js2-node (cons js2-TYPEOF "typeof") (cons js2-INSTANCEOF "instanceof") (cons js2-DELPROP "delete") + (cons js2-AWAIT "await") + (cons js2-VOID "void") (cons js2-COMMA ",") (cons js2-COLON ":") (cons js2-OR "||") @@@ -3530,7 -3566,7 +3566,7 @@@ The type field holds the actual assignm len operand))) "AST node type for unary operator nodes. The type field can be NOT, BITNOT, POS, NEG, INC, DEC, - TYPEOF, DELPROP or TRIPLEDOT. For INC or DEC, a 'postfix node + TYPEOF, DELPROP, TRIPLEDOT or AWAIT. For INC or DEC, a 'postfix node property is added if the operator follows the operand." operand) ; a `js2-node' expression @@@ -3550,7 -3586,9 +3586,9 @@@ (unless postfix (insert op)) (if (or (= tt js2-TYPEOF) - (= tt js2-DELPROP)) + (= tt js2-DELPROP) + (= tt js2-AWAIT) + (= tt js2-VOID)) (insert " ")) (js2-print-ast (js2-unary-node-operand n) 0) (when postfix @@@ -3686,11 -3724,14 +3724,14 @@@ Returns 0 if NODE is nil or its identif (num-value (js2-token-number (js2-current-token))) (num-base (js2-token-number-base - (js2-current-token)))))) + (js2-current-token))) + (legacy-octal-p (js2-token-number-legacy-octal-p + (js2-current-token)))))) "AST node for a number literal." value ; the original string, e.g. "6.02e23" num-value ; the parsed number value - num-base) ; the number's base + num-base ; the number's base + legacy-octal-p) ; whether the number is a legacy octal (0123 instead of 0o123) (put 'cl-struct-js2-number-node 'js2-visitor 'js2-visit-none) (put 'cl-struct-js2-number-node 'js2-printer 'js2-print-number-node) @@@ -3810,9 -3851,32 +3851,32 @@@ You can tell the quote type by looking (insert ","))) (insert "]")) - (cl-defstruct (js2-class-node + (cl-defstruct (js2-object-node (:include js2-node) (:constructor nil) + (:constructor make-js2-object-node (&key (type js2-OBJECTLIT) + (pos js2-ts-cursor) + len + elems))) + "AST node for an object literal expression. + `elems' is a list of `js2-object-prop-node'." + elems) + + (put 'cl-struct-js2-object-node 'js2-visitor 'js2-visit-object-node) + (put 'cl-struct-js2-object-node 'js2-printer 'js2-print-object-node) + + (defun js2-visit-object-node (n v) + (dolist (e (js2-object-node-elems n)) + (js2-visit-ast e v))) + + (defun js2-print-object-node (n i) + (insert (js2-make-pad i) "{") + (js2-print-list (js2-object-node-elems n)) + (insert "}")) + + (cl-defstruct (js2-class-node + (:include js2-object-node) + (:constructor nil) (:constructor make-js2-class-node (&key (type js2-CLASS) (pos js2-ts-cursor) (form 'CLASS_STATEMENT) @@@ -3824,7 -3888,7 +3888,7 @@@ optional `js2-expr-node' form ; CLASS_{STATEMENT|EXPRESSION} name ; class name (a `js2-node-name', or nil if anonymous) extends ; class heritage (a `js2-expr-node', or nil if none) - elems) + ) (put 'cl-struct-js2-class-node 'js2-visitor 'js2-visit-class-node) (put 'cl-struct-js2-class-node 'js2-printer 'js2-print-class-node) @@@ -3856,28 -3920,29 +3920,29 @@@ (js2-print-ast elem (1+ i)))) (insert "\n" pad "}"))) - (cl-defstruct (js2-object-node + (cl-defstruct (js2-computed-prop-name-node (:include js2-node) (:constructor nil) - (:constructor make-js2-object-node (&key (type js2-OBJECTLIT) - (pos js2-ts-cursor) - len - elems))) - "AST node for an object literal expression. - `elems' is a list of `js2-object-prop-node'." - elems) + (:constructor make-js2-computed-prop-name-node + (&key + (type js2-LB) + expr + (pos (js2-current-token-beg)) + (len (- js2-ts-cursor + (js2-current-token-beg)))))) + "AST node for a `ComputedPropertyName'." + expr) - (put 'cl-struct-js2-object-node 'js2-visitor 'js2-visit-object-node) - (put 'cl-struct-js2-object-node 'js2-printer 'js2-print-object-node) + (put 'cl-struct-js2-computed-prop-name-node 'js2-visitor 'js2-visit-computed-prop-name-node) + (put 'cl-struct-js2-computed-prop-name-node 'js2-printer 'js2-print-computed-prop-name-node) - (defun js2-visit-object-node (n v) - (dolist (e (js2-object-node-elems n)) - (js2-visit-ast e v))) + (defun js2-visit-computed-prop-name-node (n v) + (js2-visit-ast (js2-computed-prop-name-node-expr n) v)) - (defun js2-print-object-node (n i) - (insert (js2-make-pad i) "{") - (js2-print-list (js2-object-node-elems n)) - (insert "}")) + (defun js2-print-computed-prop-name-node (n i) + (insert (js2-make-pad i) "[") + (js2-print-ast (js2-computed-prop-name-node-expr n) 0) + (insert "]")) (cl-defstruct (js2-object-prop-node (:include js2-infix-node) @@@ -3898,53 -3963,41 +3963,41 @@@ both fields have the same value." (defun js2-print-object-prop-node (n i) (let* ((left (js2-object-prop-node-left n)) - (right (js2-object-prop-node-right n)) - (computed (not (or (js2-string-node-p left) - (js2-number-node-p left) - (js2-name-node-p left))))) - (insert (js2-make-pad i)) - (if computed - (insert "[")) - (js2-print-ast left 0) - (if computed - (insert "]")) + (right (js2-object-prop-node-right n))) + (js2-print-ast left i) (if (not (js2-node-get-prop n 'SHORTHAND)) (progn (insert ": ") (js2-print-ast right 0))))) - (cl-defstruct (js2-getter-setter-node + (cl-defstruct (js2-method-node (:include js2-infix-node) (:constructor nil) - (:constructor make-js2-getter-setter-node (&key type ; GET, SET, or FUNCTION - (pos js2-ts-cursor) - len left right))) - "AST node for a getter/setter property in an object literal. - The `left' field is the `js2-name-node' naming the getter/setter prop. + (:constructor make-js2-method-node (&key (pos js2-ts-cursor) + len left right))) + "AST node for a method in an object literal or a class body. + The `left' field is the `js2-name-node' naming the method. The `right' field is always an anonymous `js2-function-node' with a node - property `GETTER_SETTER' set to js2-GET, js2-SET, or js2-FUNCTION. ") + property `METHOD_TYPE' set to 'GET or 'SET. ") - (put 'cl-struct-js2-getter-setter-node 'js2-visitor 'js2-visit-infix-node) - (put 'cl-struct-js2-getter-setter-node 'js2-printer 'js2-print-getter-setter) + (put 'cl-struct-js2-method-node 'js2-visitor 'js2-visit-infix-node) + (put 'cl-struct-js2-method-node 'js2-printer 'js2-print-method) - (defun js2-print-getter-setter (n i) + (defun js2-print-method (n i) (let* ((pad (js2-make-pad i)) - (left (js2-getter-setter-node-left n)) - (right (js2-getter-setter-node-right n)) - (computed (not (or (js2-string-node-p left) - (js2-number-node-p left) - (js2-name-node-p left))))) + (left (js2-method-node-left n)) + (right (js2-method-node-right n)) + (type (js2-node-get-prop right 'METHOD_TYPE))) (insert pad) - (if (/= (js2-node-type n) js2-FUNCTION) - (insert (if (= (js2-node-type n) js2-GET) "get " "set "))) + (when type + (insert (cdr (assoc type '((GET . "get ") + (SET . "set ") + (ASYNC . "async ") + (FUNCTION . "")))))) (when (and (js2-function-node-p right) (eq 'STAR (js2-function-node-generator-type right))) (insert "*")) - (when computed - (insert "[")) (js2-print-ast left 0) - (when computed - (insert "]")) (js2-print-ast right 0))) (cl-defstruct (js2-prop-get-node @@@ -5106,6 -5159,8 +5159,8 @@@ You should use `js2-print-tree' instea (or (js2-node-has-side-effects expr) (when (js2-string-node-p expr) (member (js2-string-node-value expr) '("use strict" "use asm")))))) + ((= tt js2-AWAIT) + (js2-node-has-side-effects (js2-unary-node-operand node))) ((= tt js2-COMMA) (js2-node-has-side-effects (js2-infix-node-right node))) ((or (= tt js2-AND) @@@ -5855,7 -5910,7 +5910,7 @@@ its relevant fields and puts it into `j (let (identifier-start is-unicode-escape-start c contains-escape escape-val str result base - look-for-slash continue tt + look-for-slash continue tt legacy-octal (token (js2-new-token 0))) (setq tt @@@ -5977,6 -6032,7 +6032,7 @@@ ((and (or (eq c ?o) (eq c ?O)) (>= js2-language-version 200)) (setq base 8) + (setq legacy-octal nil) (setq c (js2-get-char))) ((js2-digit-p c) (setq base 'maybe-8)) @@@ -6014,7 -6070,8 +6070,8 @@@ (js2-add-to-string c) (setq c (js2-get-char))) (when (eq base 'maybe-8) - (setq base 8)))) + (setq base 8 + legacy-octal t)))) (when (and (eq base 10) (memq c '(?. ?e ?E))) (when (eq c ?.) (cl-loop do @@@ -6036,7 -6093,8 +6093,8 @@@ (js2-unget-char) (let ((str (js2-set-string-from-buffer token))) (setf (js2-token-number token) (js2-string-to-number str base) - (js2-token-number-base token) base)) + (js2-token-number-base token) base + (js2-token-number-legacy-octal-p token) (and (= base 8) legacy-octal))) (throw 'return js2-NUMBER)) ;; is it a string? (when (or (memq c '(?\" ?\')) @@@ -6753,6 -6811,8 +6811,8 @@@ Shown at or above `js2-highlight-level (prop (if (string-match js2-ecma-object-props prop-name) 'font-lock-constant-face)))))) + (when (and (not face) target (not call-p) prop-name) + (setq face 'js2-object-property)) (when face (let ((pos (+ (js2-node-pos parent) ; absolute (js2-node-pos prop)))) ; relative @@@ -6852,15 -6912,18 +6912,18 @@@ of a simple name. Called before EXPR h '("alias" "augments" "borrows" + "callback" "bug" "base" "config" "default" "define" "exception" + "func" "function" "member" "memberOf" + "method" "name" "namespace" "since" @@@ -6891,6 -6954,7 +6954,7 @@@ "export" "fileoverview" "final" + "func" "function" "hidden" "ignore" @@@ -6899,6 -6963,7 +6963,7 @@@ "inner" "interface" "license" + "method" "noalias" "noshadow" "notypecheck" @@@ -7199,11 -7264,12 +7264,12 @@@ are ignored. js2-additional-externs))) (defun js2-get-jslint-globals () + (js2-reparse) (cl-loop for node in (js2-ast-root-comments js2-mode-ast) when (and (eq 'block (js2-comment-node-format node)) (save-excursion (goto-char (js2-node-abs-pos node)) - (looking-at "/\\*global "))) + (looking-at "/\\* *global "))) append (js2-get-jslint-globals-in (match-end 0) (js2-node-abs-end node)))) @@@ -7395,22 -7461,23 +7461,23 @@@ We do a depth-first traversal of NODE we append the property name to QNAME, then call `js2-record-imenu-entry'." (let (right) (dolist (e (js2-object-node-elems node)) ; e is a `js2-object-prop-node' - (let ((left (js2-infix-node-left e)) - ;; Element positions are relative to the parent position. - (pos (+ pos (js2-node-pos e)))) - (cond - ;; foo: function() {...} - ((js2-function-node-p (setq right (js2-infix-node-right e))) - (when (js2-prop-node-name left) - ;; As a policy decision, we record the position of the property, - ;; not the position of the `function' keyword, since the property - ;; is effectively the name of the function. - (js2-record-imenu-entry right (append qname (list left)) pos))) - ;; foo: {object-literal} -- add foo to qname, offset position, and recurse - ((js2-object-node-p right) - (js2-record-object-literal right - (append qname (list (js2-infix-node-left e))) - (+ pos (js2-node-pos right))))))))) + (when (js2-infix-node-p e) + (let ((left (js2-infix-node-left e)) + ;; Element positions are relative to the parent position. + (pos (+ pos (js2-node-pos e)))) + (cond + ;; foo: function() {...} + ((js2-function-node-p (setq right (js2-infix-node-right e))) + (when (js2-prop-node-name left) + ;; As a policy decision, we record the position of the property, + ;; not the position of the `function' keyword, since the property + ;; is effectively the name of the function. + (js2-record-imenu-entry right (append qname (list left)) pos))) + ;; foo: {object-literal} -- add foo to qname, offset position, and recurse + ((js2-object-node-p right) + (js2-record-object-literal right + (append qname (list (js2-infix-node-left e))) + (+ pos (js2-node-pos right)))))))))) (defun js2-node-top-level-decl-p (node) "Return t if NODE's name is defined in the top-level scope. @@@ -7452,7 -7519,7 +7519,7 @@@ For instance, processing a nested scop (let (result fn parent-qname p elem) (dolist (entry js2-imenu-recorder) ;; function node goes first - (cl-destructuring-bind (current-fn &rest (&whole chain head &rest)) entry + (cl-destructuring-bind (current-fn &rest (&whole chain head &rest _)) entry ;; 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)) @@@ -7676,14 -7743,67 +7743,67 @@@ Returns nil and consumes nothing if MAT (defun js2-match-contextual-kwd (name) "Consume and return t if next token is `js2-NAME', and its string is NAME. Returns nil and keeps current token otherwise." - (if (or (/= (js2-get-token) js2-NAME) - (not (string= (js2-current-token-string) name))) - (progn - (js2-unget-token) - nil) + (if (js2-contextual-kwd-p (progn (js2-get-token) + (js2-current-token)) + name) + (progn (js2-record-face 'font-lock-keyword-face) t) + (js2-unget-token) + nil)) + + (defun js2-contextual-kwd-p (token name) + "Return t if TOKEN is `js2-NAME', and its string is NAME." + (and (= (js2-token-type token) js2-NAME) + (string= (js2-token-string token) name))) + + (defun js2-match-async-function () + (when (and (js2-contextual-kwd-p (js2-current-token) "async") + (= (js2-peek-token) js2-FUNCTION)) (js2-record-face 'font-lock-keyword-face) + (js2-get-token) t)) + (defun js2-match-async-arrow-function () + (and (js2-contextual-kwd-p (js2-current-token) "async") + (/= (js2-peek-token) js2-FUNCTION))) + + (defsubst js2-inside-function () + (cl-plusp js2-nesting-of-function)) + + (defsubst js2-inside-async-function () + (and (js2-inside-function) + (js2-function-node-async js2-current-script-or-fn))) + + (defun js2-parse-await-maybe (tt) + "Parse \"await\" as an AwaitExpression, if it is one." + (and (= tt js2-NAME) + (js2-contextual-kwd-p (js2-current-token) "await") + ;; Per the proposal, AwaitExpression consists of "await" + ;; followed by a UnaryExpression. So look ahead for one. + (let ((ts-state (make-js2-ts-state)) + (recorded-identifiers js2-recorded-identifiers) + (parsed-errors js2-parsed-errors) + (current-token (js2-current-token)) + (beg (js2-current-token-beg)) + (end (js2-current-token-end)) + pn) + (js2-get-token) + (setq pn (js2-make-unary js2-AWAIT 'js2-parse-unary-expr)) + (if (= (js2-node-type (js2-unary-node-operand pn)) js2-ERROR) + ;; The parse failed, so pretend like nothing happened and restore + ;; the previous parsing state. + (progn + (js2-ts-seek ts-state) + (setq js2-recorded-identifiers recorded-identifiers + js2-parsed-errors parsed-errors) + ;; And ensure the caller knows about the failure. + nil) + ;; The parse was successful, so process and return the "await". + (js2-record-face 'font-lock-keyword-face current-token) + (unless (js2-inside-async-function) + (js2-report-error "msg.bad.await" nil + beg (- end beg))) + pn)))) + (defun js2-get-prop-name-token () (js2-get-token (and (>= js2-language-version 170) 'KEYWORD_IS_NAME))) @@@ -7734,9 -7854,6 +7854,6 @@@ Returns t on match, nil if no match. (js2-unget-token)) nil)) - (defsubst js2-inside-function () - (cl-plusp js2-nesting-of-function)) - (defun js2-set-requires-activation () (if (js2-function-node-p js2-current-script-or-fn) (setf (js2-function-node-needs-activation js2-current-script-or-fn) t))) @@@ -7876,7 -7993,7 +7993,7 @@@ Scanner should be initialized. js2-nesting-of-function 0 js2-labeled-stmt nil js2-recorded-identifiers nil ; for js2-highlight - js2-in-use-strict-directive nil) + js2-in-use-strict-directive js2-mode-assume-strict) (while (/= (setq tt (js2-get-token)) js2-EOF) (if (= tt js2-FUNCTION) (progn @@@ -7987,16 -8104,34 +8104,34 @@@ declared; probably to check them for er (list node))) ((js2-object-node-p node) (dolist (elem (js2-object-node-elems node)) - (when (js2-object-prop-node-p elem) - (push (js2-define-destruct-symbols - ;; In abbreviated destructuring {a, b}, right == left. - (js2-object-prop-node-right elem) - decl-type face ignore-not-in-block) - name-nodes))) + (let ((subexpr (cond + ((and (js2-infix-node-p elem) + (= js2-ASSIGN (js2-infix-node-type elem))) + ;; Destructuring with default argument. + (js2-infix-node-left elem)) + ((and (js2-infix-node-p elem) + (= js2-COLON (js2-infix-node-type elem))) + ;; In regular destructuring {a: aa, b: bb}, + ;; the var is on the right. In abbreviated + ;; destructuring {a, b}, right == left. + (js2-infix-node-right elem)) + ((and (js2-unary-node-p elem) + (= js2-TRIPLEDOT (js2-unary-node-type elem))) + ;; Destructuring with spread. + (js2-unary-node-operand elem))))) + (when subexpr + (push (js2-define-destruct-symbols + subexpr decl-type face ignore-not-in-block) + name-nodes)))) (apply #'append (nreverse name-nodes))) ((js2-array-node-p node) (dolist (elem (js2-array-node-elems node)) (when elem + (setq elem (cond ((js2-infix-node-p elem) ;; default (=) + (js2-infix-node-left elem)) + ((js2-unary-node-p elem) ;; rest (...) + (js2-unary-node-operand elem)) + (t elem))) (push (js2-define-destruct-symbols elem decl-type face ignore-not-in-block) name-nodes))) @@@ -8053,8 -8188,7 +8188,7 @@@ represented by FN-NODE at POS. new-param-name-nodes (js2-define-destruct-symbols param js2-LP 'js2-function-param)) (js2-check-strict-function-params param-name-nodes new-param-name-nodes) - (setq param-name-nodes (append param-name-nodes new-param-name-nodes)) - (push param params)) + (setq param-name-nodes (append param-name-nodes new-param-name-nodes))) ;; variable name (t (when (and (>= js2-language-version 200) @@@ -8068,22 -8202,23 +8202,23 @@@ (setq param (js2-create-name-node)) (js2-define-symbol js2-LP (js2-current-token-string) param) (js2-check-strict-function-params param-name-nodes (list param)) - (setq param-name-nodes (append param-name-nodes (list param))) - ;; default parameter value - (when (and (>= js2-language-version 200) - (js2-match-token js2-ASSIGN)) - (cl-assert (not paren-free-arrow)) - (let* ((pos (js2-node-pos param)) - (tt (js2-current-token-type)) - (op-pos (- (js2-current-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)) - (js2-node-add-children param left right))) - (push param params))) + (setq param-name-nodes (append param-name-nodes (list param))))) + ;; default parameter value + (when (and (not rest-param-at) + (>= js2-language-version 200) + (js2-match-token js2-ASSIGN)) + (cl-assert (not paren-free-arrow)) + (let* ((pos (js2-node-pos param)) + (tt (js2-current-token-type)) + (op-pos (- (js2-current-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)) + (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))) @@@ -8116,7 -8251,7 +8251,7 @@@ Last token scanned is the close-curly f (js2-name-node-name name) pos end) (js2-add-strict-warning "msg.anon.no.return.value" nil pos end))))) - (defun js2-parse-function-stmt () + (defun js2-parse-function-stmt (&optional async-p) (let ((pos (js2-current-token-beg)) (star-p (js2-match-token js2-MUL))) (js2-must-match-name "msg.unnamed.function.stmt") @@@ -8124,28 -8259,31 +8259,31 @@@ pn member-expr) (cond ((js2-match-token js2-LP) - (js2-parse-function 'FUNCTION_STATEMENT pos star-p name)) + (js2-parse-function 'FUNCTION_STATEMENT pos star-p async-p name)) (js2-allow-member-expr-as-function-name (setq member-expr (js2-parse-member-expr-tail nil name)) (js2-parse-highlight-member-expr-fn-name member-expr) (js2-must-match js2-LP "msg.no.paren.parms") - (setf pn (js2-parse-function 'FUNCTION_STATEMENT pos star-p) + (setf pn (js2-parse-function 'FUNCTION_STATEMENT pos star-p async-p) (js2-function-node-member-expr pn) member-expr) pn) (t (js2-report-error "msg.no.paren.parms") (make-js2-error-node)))))) - (defun js2-parse-function-expr () + (defun js2-parse-async-function-stmt () + (js2-parse-function-stmt t)) + + (defun js2-parse-function-expr (&optional async-p) (let ((pos (js2-current-token-beg)) (star-p (js2-match-token js2-MUL)) name) (when (js2-match-token js2-NAME) (setq name (js2-create-name-node t))) (js2-must-match js2-LP "msg.no.paren.parms") - (js2-parse-function 'FUNCTION_EXPRESSION pos star-p name))) + (js2-parse-function 'FUNCTION_EXPRESSION pos star-p async-p name))) - (defun js2-parse-function-internal (function-type pos star-p &optional name) + (defun js2-parse-function-internal (function-type pos star-p &optional async-p name) (let (fn-node lp) (if (= (js2-current-token-type) js2-LP) ; eventually matched LP? (setq lp (js2-current-token-beg))) @@@ -8153,7 -8291,8 +8291,8 @@@ :name name :form function-type :lp (if lp (- lp pos)) - :generator-type (and star-p 'STAR))) + :generator-type (and star-p 'STAR) + :async async-p)) (when name (js2-set-face (js2-node-pos name) (js2-node-end name) 'font-lock-function-name-face 'record) @@@ -8208,7 -8347,7 +8347,7 @@@ (setf (js2-scope-parent-scope fn-node) js2-current-scope) fn-node)) - (defun js2-parse-function (function-type pos star-p &optional name) + (defun js2-parse-function (function-type pos star-p &optional async-p name) "Function parser. FUNCTION-TYPE is a symbol, POS is the beginning of the first token (function keyword, unless it's an arrow function), NAME is js2-name-node." @@@ -8224,7 -8363,7 +8363,7 @@@ (setq ts-state (make-js2-ts-state)) (setq continue (catch 'reparse (setq fn-node (js2-parse-function-internal - function-type pos star-p name)) + function-type pos star-p async-p name)) ;; Don't continue. nil)) (when continue @@@ -8334,9 -8473,12 +8473,12 @@@ node are given relative start position (defun js2-statement-helper () (let* ((tt (js2-get-token)) (first-tt tt) + (async-stmt (js2-match-async-function)) (parser (if (= tt js2-ERROR) #'js2-parse-semi - (aref js2-parsers tt))) + (if async-stmt + #'js2-parse-async-function-stmt + (aref js2-parsers tt)))) pn) ;; If the statement is set, then it's been told its label by now. (and js2-labeled-stmt @@@ -8345,7 -8487,8 +8487,8 @@@ (setq pn (funcall parser)) ;; Don't do auto semi insertion for certain statement types. (unless (or (memq first-tt js2-no-semi-insertion) - (js2-labeled-stmt-node-p pn)) + (js2-labeled-stmt-node-p pn) + async-stmt) (js2-auto-insert-semicolon pn)) pn)) @@@ -8474,7 -8617,7 +8617,7 @@@ imports or a namespace import that foll (js2-define-symbol js2-LET (js2-name-node-name name-node) name-node t)))))) ((= (js2-peek-token) js2-NAME) - (let ((binding (js2-maybe-parse-export-binding))) + (let ((binding (js2-maybe-parse-export-binding t))) (let ((node-name (js2-export-binding-node-local-name binding))) (js2-define-symbol js2-LET (js2-name-node-name node-name) node-name t)) (setf (js2-import-clause-node-default-binding clause) binding) @@@ -8507,50 -8650,47 +8650,47 @@@ "Parse a namespace import expression such as '* as bar'. The current token must be js2-MUL." (let ((beg (js2-current-token-beg))) - (when (js2-must-match js2-NAME "msg.syntax") - (if (equal "as" (js2-current-token-string)) - (when (js2-must-match-prop-name "msg.syntax") - (let ((node (make-js2-namespace-import-node - :pos beg - :len (- (js2-current-token-end) beg) - :name (make-js2-name-node - :pos (js2-current-token-beg) - :len (js2-current-token-end) - :name (js2-current-token-string))))) - (js2-node-add-children node (js2-namespace-import-node-name node)) - node)) - (js2-unget-token) - (js2-report-error "msg.syntax"))))) + (if (js2-match-contextual-kwd "as") + (when (js2-must-match-prop-name "msg.syntax") + (let ((node (make-js2-namespace-import-node + :pos beg + :len (- (js2-current-token-end) beg) + :name (make-js2-name-node + :pos (js2-current-token-beg) + :len (js2-current-token-end) + :name (js2-current-token-string))))) + (js2-node-add-children node (js2-namespace-import-node-name node)) + node)) + (js2-unget-token) + (js2-report-error "msg.syntax")))) (defun js2-parse-from-clause () "Parse the from clause in an import or export statement. E.g. from 'src/lib'" - (when (js2-must-match-name "msg.mod.from.after.import.spec.set") - (let ((beg (js2-current-token-beg))) - (if (equal "from" (js2-current-token-string)) - (cond - ((js2-match-token js2-STRING) - (make-js2-from-clause-node - :pos beg - :len (- (js2-current-token-end) beg) - :module-id (js2-current-token-string) - :metadata-p nil)) - ((js2-match-token js2-THIS) - (when (js2-must-match-name "msg.mod.spec.after.from") - (if (equal "module" (js2-current-token-string)) - (make-js2-from-clause-node - :pos beg - :len (- (js2-current-token-end) beg) - :module-id "this" - :metadata-p t) - (js2-unget-token) - (js2-unget-token) - (js2-report-error "msg.mod.spec.after.from") - nil))) - (t (js2-report-error "msg.mod.spec.after.from") nil)) - (js2-unget-token) - (js2-report-error "msg.mod.from.after.import.spec.set") - nil)))) + (if (js2-match-contextual-kwd "from") + (let ((beg (js2-current-token-beg))) + (cond + ((js2-match-token js2-STRING) + (make-js2-from-clause-node + :pos beg + :len (- (js2-current-token-end) beg) + :module-id (js2-current-token-string) + :metadata-p nil)) + ((js2-match-token js2-THIS) + (when (js2-must-match-name "msg.mod.spec.after.from") + (if (equal "module" (js2-current-token-string)) + (make-js2-from-clause-node + :pos beg + :len (- (js2-current-token-end) beg) + :module-id "this" + :metadata-p t) + (js2-unget-token) + (js2-unget-token) + (js2-report-error "msg.mod.spec.after.from") + nil))) + (t (js2-report-error "msg.mod.spec.after.from") nil))) + (js2-report-error "msg.mod.from.after.import.spec.set") + nil)) (defun js2-parse-export-bindings (&optional import-p) "Parse a list of export binding expressions such as {}, {foo, bar}, and @@@ -8558,7 -8698,7 +8698,7 @@@ js2-LC. Return a lisp list of js2-export-binding-node" (let ((bindings (list))) (while - (let ((binding (js2-maybe-parse-export-binding))) + (let ((binding (js2-maybe-parse-export-binding import-p))) (when binding (push binding bindings)) (js2-match-token js2-COMMA))) @@@ -8567,7 -8707,7 +8707,7 @@@ "msg.mod.rc.after.export.spec.list")) (reverse bindings)))) - (defun js2-maybe-parse-export-binding () + (defun js2-maybe-parse-export-binding (&optional import-p) "Attempt to parse a binding expression found inside an import/export statement. This can take the form of either as single js2-NAME token as in 'foo' or as in a rebinding expression 'bar as foo'. If it matches, it will return an instance of @@@ -8579,45 -8719,49 +8719,49 @@@ consumes no tokens. (is-reserved-name (or (= (js2-current-token-type) js2-RESERVED) (aref js2-kwd-tokens (js2-current-token-type))))) (if extern-name - (let ((as (and (js2-match-token js2-NAME) (js2-current-token-string)))) - (if (and as (equal "as" (js2-current-token-string))) - (let ((name - (or - (and (js2-match-token js2-DEFAULT) "default") - (and (js2-match-token js2-NAME) (js2-current-token-string))))) - (if name - (let ((node (make-js2-export-binding-node - :pos beg - :len (- (js2-current-token-end) beg) - :local-name (make-js2-name-node - :name name - :pos (js2-current-token-beg) - :len (js2-current-token-len)) - :extern-name (make-js2-name-node - :name extern-name - :pos beg - :len extern-name-len)))) - (js2-node-add-children - node - (js2-export-binding-node-local-name node) - (js2-export-binding-node-extern-name node)) - node) - (js2-unget-token) - nil)) - (when as (js2-unget-token)) - (let* ((name-node (make-js2-name-node - :name (js2-current-token-string) - :pos (js2-current-token-beg) - :len (js2-current-token-len))) - (node (make-js2-export-binding-node - :pos (js2-current-token-beg) - :len (js2-current-token-len) - :local-name name-node - :extern-name name-node))) - (when is-reserved-name - (js2-report-error "msg.mod.as.after.reserved.word" extern-name)) - (js2-node-add-children node name-node) - node))) + (if (js2-match-contextual-kwd "as") + (let ((name + (or + (and (js2-match-token js2-DEFAULT) "default") + (and (js2-match-token js2-NAME) (js2-current-token-string))))) + (if name + (let ((node (make-js2-export-binding-node + :pos beg + :len (- (js2-current-token-end) beg) + :local-name (make-js2-name-node + :name name + :pos (js2-current-token-beg) + :len (js2-current-token-len)) + :extern-name (make-js2-name-node + :name extern-name + :pos beg + :len extern-name-len)))) + (js2-node-add-children + node + (js2-export-binding-node-local-name node) + (js2-export-binding-node-extern-name node)) + (if import-p + (js2-set-face (js2-current-token-beg) (js2-current-token-end) + 'font-lock-variable-name-face 'record)) + node) + (js2-unget-token) + nil)) + (let* ((name-node (make-js2-name-node + :name (js2-current-token-string) + :pos (js2-current-token-beg) + :len (js2-current-token-len))) + (node (make-js2-export-binding-node + :pos (js2-current-token-beg) + :len (js2-current-token-len) + :local-name name-node + :extern-name name-node))) + (when is-reserved-name + (js2-report-error "msg.mod.as.after.reserved.word" extern-name)) + (js2-node-add-children node name-node) + (if import-p + (js2-set-face (js2-current-token-beg) (js2-current-token-end) + 'font-lock-variable-name-face 'record)) + node)) nil))) (defun js2-parse-switch () @@@ -8745,27 -8889,45 +8889,45 @@@ invalid export statements. (when exports-list (dolist (export exports-list) (push export children))) - (when (js2-match-token js2-NAME) - (if (equal "from" (js2-current-token-string)) - (progn - (js2-unget-token) - (setq from-clause (js2-parse-from-clause))) - (js2-unget-token)))) + (when (js2-match-contextual-kwd "from") + (js2-unget-token) + (setq from-clause (js2-parse-from-clause)))) ((js2-match-token js2-DEFAULT) - (setq default (js2-parse-expr))) + (setq default (cond ((js2-match-token js2-CLASS) + (js2-parse-class-stmt)) + ((js2-match-token js2-NAME) + (if (js2-match-async-function) + (js2-parse-async-function-stmt) + (js2-unget-token) + (js2-parse-expr))) + ((js2-match-token js2-FUNCTION) + (js2-parse-function-stmt)) + (t (js2-parse-expr))))) ((or (js2-match-token js2-VAR) (js2-match-token js2-CONST) (js2-match-token js2-LET)) (setq declaration (js2-parse-variables (js2-current-token-type) (js2-current-token-beg)))) + ((js2-match-token js2-CLASS) + (setq declaration (js2-parse-class-stmt))) + ((js2-match-token js2-NAME) + (setq declaration + (if (js2-match-async-function) + (js2-parse-async-function-stmt) + (js2-unget-token) + (js2-parse-expr)))) + ((js2-match-token js2-FUNCTION) + (setq declaration (js2-parse-function-stmt))) (t (setq declaration (js2-parse-expr)))) (when from-clause (push from-clause children)) (when declaration (push declaration children) - (when (not (js2-function-node-p declaration)) + (when (not (or (js2-function-node-p declaration) + (js2-class-node-p declaration))) (js2-auto-insert-semicolon declaration))) (when default (push default children) - (when (not (js2-function-node-p default)) + (when (not (or (js2-function-node-p default) + (js2-class-node-p default))) (js2-auto-insert-semicolon default))) (let ((node (make-js2-export-node :pos beg @@@ -8810,7 -8972,7 +8972,7 @@@ Last matched token must be js2-FOR. ((= tt js2-SEMI) (js2-unget-token) (setq init (make-js2-empty-expr-node))) - ((or (= tt js2-VAR) (= tt js2-LET)) + ((or (= tt js2-VAR) (= tt js2-LET) (= tt js2-CONST)) (setq init (js2-parse-variables tt (js2-current-token-beg)))) (t (js2-unget-token) @@@ -9448,16 -9610,10 +9610,10 @@@ If NODE is non-nil, it is the AST node (pos (if node (js2-node-abs-pos node))) (len (if node (js2-node-len node)))) (cond - ((and symbol ; already defined - (or (if js2-in-use-strict-directive - ;; two const-bound vars in this block have same name - (and (= sdt js2-CONST) - (eq defining-scope js2-current-scope)) - (or (= sdt js2-CONST) ; old version is const - (= decl-type js2-CONST))) ; new version is const - ;; two let-bound vars in this block have same name - (and (= sdt js2-LET) - (eq defining-scope js2-current-scope)))) + ((and symbol ; already defined in this block + (or (= sdt js2-LET) + (= sdt js2-CONST)) + (eq defining-scope js2-current-scope)) (js2-report-error (cond ((= sdt js2-CONST) "msg.const.redecl") @@@ -9467,9 -9623,7 +9623,7 @@@ (t "msg.parm.redecl")) name pos len)) ((or (= decl-type js2-LET) - ;; strict mode const is scoped to the current LexicalEnvironment - (and js2-in-use-strict-directive - (= decl-type js2-CONST))) + (= decl-type js2-CONST)) (if (and (= decl-type js2-LET) (not ignore-not-in-block) (or (= (js2-node-type js2-current-scope) js2-IF) @@@ -9477,10 -9631,7 +9631,7 @@@ (js2-report-error "msg.let.decl.not.in.block") (js2-define-new-symbol decl-type name node))) ((or (= decl-type js2-VAR) - (= decl-type js2-FUNCTION) - ;; sloppy mode const is scoped to the current VariableEnvironment - (and (not js2-in-use-strict-directive) - (= decl-type js2-CONST))) + (= decl-type js2-FUNCTION)) (if symbol (if (and js2-strict-var-redeclaration-warning (= sdt js2-VAR)) (js2-add-strict-warning "msg.var.redecl" name) @@@ -9548,9 -9699,20 +9699,20 @@@ (let ((tt (js2-get-token)) (pos (js2-current-token-beg)) pn left right op-pos - ts-state recorded-identifiers parsed-errors) + ts-state recorded-identifiers parsed-errors + async-p) (if (= tt js2-YIELD) (js2-parse-return-or-yield tt t) + ;; TODO(mooz): Bit confusing. + ;; If we meet `async` token and it's not part of `async + ;; function`, then this `async` is for a succeeding async arrow + ;; function. + ;; Since arrow function parsing doesn't rely on neither + ;; `js2-parse-function-stmt' nor `js2-parse-function-expr' that + ;; interpret `async` token, we trash `async` and just remember + ;; we met `async` keyword to `async-p'. + (when (js2-match-async-arrow-function) + (setq async-p t)) ;; Save the tokenizer state in case we find an arrow function ;; and have to rewind. (setq ts-state (make-js2-ts-state) @@@ -9584,9 -9746,12 +9746,12 @@@ ((and (= tt js2-ARROW) (>= js2-language-version 200)) (js2-ts-seek ts-state) + (when async-p + (js2-record-face 'font-lock-keyword-face) + (js2-get-token)) (setq js2-recorded-identifiers recorded-identifiers js2-parsed-errors parsed-errors) - (setq pn (js2-parse-function 'FUNCTION_ARROW (js2-current-token-beg) nil))) + (setq pn (js2-parse-function 'FUNCTION_ARROW (js2-current-token-beg) nil async-p))) (t (js2-unget-token))) pn))) @@@ -9616,7 -9781,7 +9781,7 @@@ (js2-node-add-children pn test-expr if-true if-false)) pn)) - (defun js2-make-binary (type left parser) + (defun js2-make-binary (type left parser &optional no-get) "Helper for constructing a binary-operator AST node. LEFT is the left-side-expression, already parsed, and the binary operator should have just been matched. @@@ -9627,7 -9792,7 +9792,7 @@@ FIXME: The latter option is unused? (op-pos (- (js2-current-token-beg) pos)) (right (if (js2-node-p parser) parser - (js2-get-token) + (unless no-get (js2-get-token)) (funcall parser))) (pn (make-js2-infix-node :type type :pos pos @@@ -9816,6 -9981,7 +9981,7 @@@ to parse the operand (for prefix operat ((= tt js2-DELPROP) (js2-get-token) (js2-make-unary js2-DELPROP 'js2-parse-unary-expr)) + ((js2-parse-await-maybe tt)) ((= tt js2-ERROR) (js2-get-token) (make-js2-error-node)) ; try to continue @@@ -9968,9 -10134,9 +10134,9 @@@ Returns an expression tree that include (setq pn (js2-parse-tagged-template pn (make-js2-string-node :type tt)))) (t (js2-unget-token) - (setq continue nil)))) - (if (>= js2-highlight-level 2) - (js2-parse-highlight-member-expr-node pn)) + (setq continue nil))) + (if (>= js2-highlight-level 2) + (js2-parse-highlight-member-expr-node pn))) pn)) (defun js2-parse-tagged-template (tag-node tpl-node) @@@ -10205,6 -10371,8 +10371,8 @@@ array-literals, array comprehensions an (js2-parse-class-expr)) ((= tt js2-FUNCTION) (js2-parse-function-expr)) + ((js2-match-async-function) + (js2-parse-function-expr t)) ((= tt js2-LB) (js2-parse-array-comp-or-literal)) ((= tt js2-LC) @@@ -10221,7 -10389,8 +10389,8 @@@ ((= tt js2-NUMBER) (setq node (make-js2-number-node)) (when (and js2-in-use-strict-directive - (= (js2-number-node-num-base node) 8)) + (= (js2-number-node-num-base node) 8) + (js2-number-node-legacy-octal-p node)) (js2-report-error "msg.no.octal.strict")) node) ((or (= tt js2-STRING) (= tt js2-NO_SUBS_TEMPLATE)) @@@ -10320,19 -10489,13 +10489,13 @@@ (defun js2-parse-array-literal (pos) (let ((after-lb-or-comma t) - after-comma tt elems pn + after-comma tt elems pn was-rest (continue t)) (unless js2-is-in-destructuring (js2-push-scope (make-js2-scope))) ; for the legacy array comp (while continue (setq tt (js2-get-token)) (cond - ;; comma - ((= tt js2-COMMA) - (setq after-comma (js2-current-token-end)) - (if (not after-lb-or-comma) - (setq after-lb-or-comma t) - (push nil elems))) ;; end of array ((or (= tt js2-RB) (= tt js2-EOF)) ; prevent infinite loop @@@ -10346,21 -10509,18 +10509,18 @@@ :len (- js2-ts-cursor pos) :elems (nreverse elems))) (apply #'js2-node-add-children pn (js2-array-node-elems pn))) - ;; destructuring binding - (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-destruct-primary-expr) - ;; invalid pattern - (js2-report-error "msg.bad.var") - (make-js2-error-node)) - elems) - (setq after-lb-or-comma nil - after-comma nil)) + ;; anything after rest element (...foo) + (was-rest + (js2-report-error "msg.param.after.rest")) + ;; comma + ((= tt js2-COMMA) + (setq after-comma (js2-current-token-end)) + (if (not after-lb-or-comma) + (setq after-lb-or-comma t) + (push nil elems))) ;; array comp ((and (>= js2-language-version 170) + (not js2-is-in-destructuring) (= tt js2-FOR) ; check for array comprehension (not after-lb-or-comma) ; "for" can't follow a comma elems ; must have at least 1 element @@@ -10374,9 -10534,12 +10534,12 @@@ (js2-report-error "msg.no.bracket.arg")) (if (and (= tt js2-TRIPLEDOT) (>= js2-language-version 200)) - ;; spread operator - (push (js2-make-unary tt 'js2-parse-assign-expr) - elems) + ;; rest/spread operator + (progn + (push (js2-make-unary tt 'js2-parse-assign-expr) + elems) + (if js2-is-in-destructuring + (setq was-rest t))) (js2-unget-token) (push (js2-parse-assign-expr) elems)) (setq after-lb-or-comma nil @@@ -10526,6 -10689,7 +10689,7 @@@ If ONLY-OF-P is non-nil, only the 'for (js2-set-face (js2-node-pos name) (js2-node-end name) 'font-lock-function-name-face 'record) (let ((node (js2-parse-class pos 'CLASS_STATEMENT name))) + (js2-record-imenu-functions node name) (js2-define-symbol js2-FUNCTION (js2-name-node-name name) node) @@@ -10570,17 -10734,22 +10734,22 @@@ (defun js2-property-key-string (property-node) "Return the key of PROPERTY-NODE (a `js2-object-prop-node' or - `js2-getter-setter-node') as a string, or nil if it can't be + `js2-method-node') as a string, or nil if it can't be represented as a string (e.g., the key is computed by an expression)." - (let ((key (js2-infix-node-left property-node))) - (cond - ((js2-name-node-p key) - (js2-name-node-name key)) - ((js2-string-node-p key) - (js2-string-node-value key)) - ((js2-number-node-p key) - (js2-number-node-value key))))) + (cond + ((js2-unary-node-p property-node) nil) ;; {...foo} + (t + (let ((key (js2-infix-node-left property-node))) + (when (js2-computed-prop-name-node-p key) + (setq key (js2-computed-prop-name-node-expr key))) + (cond + ((js2-name-node-p key) + (js2-name-node-name key)) + ((js2-string-node-p key) + (js2-string-node-value key)) + ((js2-number-node-p key) + (js2-number-node-value key))))))) (defun js2-parse-object-literal-elems (&optional class-p) (let ((pos (js2-current-token-beg)) @@@ -10605,21 -10774,26 +10774,26 @@@ (= js2-MUL tt)) (setq previous-token (js2-current-token) tt (js2-get-prop-name-token))) - ;; Handle 'get' or 'set' keywords + ;; Handle getter, setter and async methods (let ((prop (js2-current-token-string))) (when (and (>= js2-language-version 200) (= js2-NAME tt) - (or (string= prop "get") - (string= prop "set")) + (member prop '("get" "set" "async")) (member (js2-peek-token) (list js2-NAME js2-STRING js2-NUMBER js2-LB))) (setq previous-token (js2-current-token) tt (js2-get-prop-name-token)))) (cond - ;; Found a property (of any sort) + ;; Rest/spread (...expr) + ((and (>= js2-language-version 200) + (not class-p) (not static) (not previous-token) + (= js2-TRIPLEDOT tt)) + (setq after-comma nil + elem (js2-make-unary js2-TRIPLEDOT 'js2-parse-assign-expr))) + ;; Found a key/value property (of any sort) ((member tt (list js2-NAME js2-STRING js2-NUMBER js2-LB)) (setq after-comma nil - elem (js2-parse-named-prop tt pos previous-token)) + elem (js2-parse-named-prop tt previous-token)) (if (and (null elem) (not js2-recover-from-parse-errors)) (setq continue nil))) @@@ -10631,6 -10805,10 +10805,10 @@@ (if after-comma (js2-parse-warn-trailing-comma "msg.extra.trailing.comma" pos elems after-comma))) + ;; Skip semicolons in a class body + ((and class-p + (= tt js2-SEMI)) + nil) (t (js2-report-error "msg.bad.prop") (unless js2-recover-from-parse-errors @@@ -10654,7 -10832,16 +10832,16 @@@ (lambda (previous-elem) (and (setq previous-elem-key-string (js2-property-key-string previous-elem)) - (string= previous-elem-key-string elem-key-string))) + ;; Check if the property is a duplicate. + (string= previous-elem-key-string elem-key-string) + ;; But make an exception for getter / setter pairs. + (not (and (js2-method-node-p elem) + (js2-method-node-p previous-elem) + (let ((type (js2-node-get-prop (js2-method-node-right elem) 'METHOD_TYPE)) + (previous-type (js2-node-get-prop (js2-method-node-right previous-elem) 'METHOD_TYPE))) + (and (member type '(GET SET)) + (member previous-type '(GET SET)) + (not (eq type previous-type)))))))) elems)) (js2-report-error "msg.dup.obj.lit.prop.strict" elem-key-string @@@ -10665,44 -10852,34 +10852,34 @@@ (js2-must-match js2-RC "msg.no.brace.prop") (nreverse elems))) - (defun js2-parse-named-prop (tt pos previous-token) + (defun js2-parse-named-prop (tt previous-token) "Parse a name, string, or getter/setter object property. When `js2-is-in-destructuring' is t, forms like {a, b, c} will be permitted." - (let ((key (cond - ;; Literal string keys: {'foo': 'bar'} - ((= tt js2-STRING) - (make-js2-string-node)) - ;; Handle computed keys: {[Symbol.iterator]: ...}, *[1+2]() {...}}, - ;; {[foo + bar]() { ... }}, {[get ['x' + 1]() {...}} - ((and (= tt js2-LB) - (>= js2-language-version 200)) - (prog1 (js2-parse-expr) - (js2-must-match js2-RB "msg.missing.computed.rb"))) - ;; Numeric keys: {12: 'foo'}, {10.7: 'bar'} - ((= tt js2-NUMBER) - (make-js2-number-node)) - ;; Unquoted names: {foo: 12} - ((= tt js2-NAME) - (js2-create-name-node)) - ;; Anything else is an error - (t (js2-report-error "msg.bad.prop")))) + (let ((key (js2-parse-prop-name tt)) (prop (and previous-token (js2-token-string previous-token))) (property-type (when previous-token (if (= (js2-token-type previous-token) js2-MUL) "*" - (js2-token-string previous-token))))) - (when (or (string= prop "get") - (string= prop "set")) + (js2-token-string previous-token)))) + pos) + (when (member prop '("get" "set" "async")) + (setq pos (js2-token-beg previous-token)) (js2-set-face (js2-token-beg previous-token) (js2-token-end previous-token) - 'font-lock-keyword-face 'record)) ; get/set + 'font-lock-keyword-face 'record)) ; get/set/async (cond ;; method definition: {f() {...}} ((and (= (js2-peek-token) js2-LP) (>= js2-language-version 200)) (when (js2-name-node-p key) ; highlight function name properties (js2-record-face 'font-lock-function-name-face)) - (js2-parse-getter-setter-prop pos key property-type)) + (js2-parse-method-prop pos key property-type)) + ;; binding element with initializer + ((and (= (js2-peek-token) js2-ASSIGN) + (>= js2-language-version 200)) + (if (not js2-is-in-destructuring) + (js2-report-error "msg.init.no.destruct")) + (js2-parse-initialized-binding key)) ;; regular prop (t (let ((beg (js2-current-token-beg)) @@@ -10717,10 -10894,38 +10894,38 @@@ (if (js2-function-node-p (js2-object-prop-node-right expr)) 'font-lock-function-name-face - 'font-lock-variable-name-face) + 'js2-object-property) 'record) expr))))) + (defun js2-parse-initialized-binding (name) + "Parse a `SingleNameBinding' with initializer. + + `name' is the `BindingIdentifier'." + (when (js2-match-token js2-ASSIGN) + (js2-make-binary js2-ASSIGN name 'js2-parse-assign-expr t))) + + (defun js2-parse-prop-name (tt) + (cond + ;; Literal string keys: {'foo': 'bar'} + ((= tt js2-STRING) + (make-js2-string-node)) + ;; Handle computed keys: {[Symbol.iterator]: ...}, *[1+2]() {...}}, + ;; {[foo + bar]() { ... }}, {[get ['x' + 1]() {...}} + ((and (= tt js2-LB) + (>= js2-language-version 200)) + (make-js2-computed-prop-name-node + :expr (prog1 (js2-parse-assign-expr) + (js2-must-match js2-RB "msg.missing.computed.rb")))) + ;; Numeric keys: {12: 'foo'}, {10.7: 'bar'} + ((= tt js2-NUMBER) + (make-js2-number-node)) + ;; Unquoted names: {foo: 12} + ((= tt js2-NAME) + (js2-create-name-node)) + ;; Anything else is an error + (t (js2-report-error "msg.bad.prop")))) + (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, @@@ -10762,11 -10967,12 +10967,12 @@@ string or expression. (js2-node-add-children result prop expr) result)))) - (defun js2-parse-getter-setter-prop (pos prop type-string) - "Parse getter or setter property in an object literal. + (defun js2-parse-method-prop (pos prop type-string) + "Parse method property in an object literal or a class body. JavaScript syntax is: - { get foo() {...}, set foo(x) {...} } + { foo(...) {...}, get foo() {...}, set foo(x) {...}, *foo(...) {...}, + async foo(...) {...} } and expression closure style is also supported @@@ -10775,26 -10981,24 +10981,24 @@@ POS is the start position of the `get' or `set' keyword. PROP is the `js2-name-node' representing the property name. TYPE-STRING is a string `get', `set', `*', or nil, indicating a found keyword." - (let ((type (cond - ((string= "get" type-string) js2-GET) - ((string= "set" type-string) js2-SET) - (t js2-FUNCTION))) - result end - (fn (js2-parse-function-expr))) - ;; it has to be an anonymous function, as we already parsed the name - (if (/= (js2-node-type fn) js2-FUNCTION) - (js2-report-error "msg.bad.prop") - (if (cl-plusp (length (js2-function-name fn))) - (js2-report-error "msg.bad.prop"))) - (js2-node-set-prop fn 'GETTER_SETTER type) ; for codegen - (when (string= type-string "*") - (setf (js2-function-node-generator-type fn) 'STAR)) + (let* ((type (or (cdr (assoc type-string '(("get" . GET) + ("set" . SET) + ("async" . ASYNC)))) + 'FUNCTION)) + result end + (pos (js2-current-token-beg)) + (_ (js2-must-match js2-LP "msg.no.paren.parms")) + (fn (js2-parse-function 'FUNCTION_EXPRESSION pos + (string= type-string "*") + (eq type 'ASYNC) + nil))) + (js2-node-set-prop fn 'METHOD_TYPE type) ; for codegen + (unless pos (setq pos (js2-node-pos prop))) (setq end (js2-node-end fn) - result (make-js2-getter-setter-node :type type - :pos pos - :len (- end pos) - :left prop - :right fn)) + result (make-js2-method-node :pos pos + :len (- end pos) + :left prop + :right fn)) (js2-node-add-children result prop fn) result)) @@@ -10815,6 -11019,68 +11019,68 @@@ And, if CHECK-ACTIVATION-P is non-nil, (js2-check-activation-name s (or token js2-NAME))) name)) + ;;; Use AST to extract semantic information + + (defun js2-get-element-index-from-array-node (elem array-node &optional hardcoded-array-index) + "Get index of ELEM from ARRAY-NODE or 0 and return it as string." + (let ((idx 0) elems (rlt hardcoded-array-index)) + (setq elems (js2-array-node-elems array-node)) + (if (and elem (not hardcoded-array-index)) + (setq rlt (catch 'nth-elt + (dolist (x elems) + ;; We know the ELEM does belong to ARRAY-NODE, + (if (eq elem x) (throw 'nth-elt idx)) + (setq idx (1+ idx))) + 0))) + (format "[%s]" rlt))) + + (defun js2-print-json-path (&optional hardcoded-array-index) + "Print the path to the JSON value under point, and save it in the kill ring. + If HARDCODED-ARRAY-INDEX provided, array index in JSON path is replaced with it." + (interactive "P") + (js2-reparse) + (let (previous-node current-node + key-name + rlt) + + ;; The `js2-node-at-point' starts scanning from AST root node. + ;; So there is no way to optimize it. + (setq current-node (js2-node-at-point)) + + (while (not (js2-ast-root-p current-node)) + (cond + ;; JSON property node + ((js2-object-prop-node-p current-node) + (setq key-name (js2-prop-node-name (js2-object-prop-node-left current-node))) + (if rlt (setq rlt (concat "." key-name rlt)) + (setq rlt (concat "." key-name)))) + + ;; Array node + ((or (js2-array-node-p current-node)) + (setq rlt (concat (js2-get-element-index-from-array-node previous-node + current-node + hardcoded-array-index) + rlt))) + + ;; Other nodes are ignored + (t)) + + ;; current node is archived + (setq previous-node current-node) + ;; Get parent node and continue the loop + (setq current-node (js2-node-parent current-node))) + + (cond + (rlt + ;; Clean the final result + (setq rlt (replace-regexp-in-string "^\\." "" rlt)) + (kill-new rlt) + (message "%s => kill-ring" rlt)) + (t + (message "No JSON path found!"))) + + rlt)) + ;;; Indentation support (bouncing) ;; In recent-enough Emacs, we reuse the indentation code from @@@ -11212,13 -11478,18 +11478,13 @@@ highlighting features of `js2-mode'. map) "Keymap used for js2 diagnostics buffers.") -(defun js2-error-buffer-mode () +(define-derived-mode js2-error-buffer-mode special-mode "JS Lint Diagnostics" "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)) + (setq buffer-read-only t)) (defun js2-error-buffer-next () "Move to next error and view it." @@@ -11247,7 -11518,7 +11513,7 @@@ "Scroll source buffer to show error at current line." (interactive) (cond - ((not (eq major-mode 'js2-error-buffer-mode)) + ((not (derived-mode-p 'js2-error-buffer-mode)) (message "Not in a js2 errors buffer")) ((not (buffer-live-p js2-source-buffer)) (message "Source buffer has been killed")) @@@ -11310,7 -11581,23 +11576,23 @@@ (run-hooks 'js2-init-hook) - (js2-reparse)) + (let ((js2-idle-timer-delay 0)) + ;; Schedule parsing for after when the mode hooks run. + (js2-mode-reset-timer))) + + ;; We may eventually want js2-jsx-mode to derive from js-jsx-mode, but that'd be + ;; a bit more complicated and it doesn't net us much yet. + ;;;###autoload + (define-derived-mode js2-jsx-mode js2-mode "JSX-IDE" + "Major mode for editing JSX code. + + To customize the indentation for this mode, set the SGML offset + variables (`sgml-basic-offset' et al) locally, like so: + + (defun set-jsx-indentation () + (setq-local sgml-basic-offset js2-basic-offset)) + (add-hook 'js2-jsx-mode-hook #'set-jsx-indentation)" + (set (make-local-variable 'indent-line-function) #'js2-jsx-indent-line)) (defun js2-mode-exit () "Exit `js2-mode' and clean up." @@@ -11647,10 -11934,7 +11929,7 @@@ PARSE-STATUS is as documented in `parse (insert "\n") (indent-to col) (insert "*/")))) - ((and single - (save-excursion - (and (zerop (forward-line 1)) - (looking-at "\\s-*//")))) + (single (indent-to col) (insert "// "))) ;; Don't need to extend the comment after all. @@@ -12297,7 -12581,10 +12576,10 @@@ it marks the next defun after the ones (defun js2-jump-to-definition (&optional arg) "Jump to the definition of an object's property, variable or function." (interactive "P") - (ring-insert find-tag-marker-ring (point-marker)) + (if (eval-when-compile (fboundp 'xref-push-marker-stack)) + (xref-push-marker-stack) + (ring-insert find-tag-marker-ring (point-marker))) + (js2-reparse) (let* ((node (js2-node-at-point)) (parent (js2-node-parent node)) (names (if (js2-prop-get-node-p parent) diff --combined packages/js2-mode/js2-old-indent.el index efc9053a2,f336005e4..f336005e4 --- a/packages/js2-mode/js2-old-indent.el +++ b/packages/js2-mode/js2-old-indent.el @@@ -54,6 -54,8 +54,8 @@@ ;;; Code: + (require 'sgml-mode) + (defvar js2-language-version) (declare-function js2-mark-safe-local "js2-mode") @@@ -129,13 -131,13 +131,13 @@@ switch statement body are indented one followed by an opening brace.") (defconst js2-indent-operator-re - (concat "[-+*/%<>&^|?:.]\\([^-+*/]\\|$\\)\\|!?=\\|" - (regexp-opt '("in" "instanceof") 'words)) + (concat "[-+*/%<>&^|?:.]\\([^-+*/.]\\|$\\)\\|!?=\\|" + (regexp-opt '("in" "instanceof") 'symbols)) "Regular expression matching operators that affect indentation of continued expressions.") (defconst js2-declaration-keyword-re - (regexp-opt '("var" "let" "const") 'words) + (regexp-opt '("var" "let" "const") 'symbols) "Regular expression matching variable declaration keywords.") (defun js2-re-search-forward-inner (regexp &optional bound count) @@@ -215,30 -217,38 +217,38 @@@ and comments have been removed. (defun js2-looking-at-operator-p () "Return non-nil if text after point is a non-comma operator." + (defvar js2-mode-identifier-re) (and (looking-at js2-indent-operator-re) - (or (not (looking-at ":")) + (or (not (eq (char-after) ?:)) (save-excursion (and (js2-re-search-backward "[?:{]\\|\\_" nil t) - (looking-at "?")))))) + (eq (char-after) ??)))) + (not (and + (eq (char-after) ?*) + ;; Generator method (possibly using computed property). + (looking-at (concat "\\* *\\(?:\\[\\|" + js2-mode-identifier-re + " *(\\)")) + (save-excursion + (js2-backward-sws) + ;; We might misindent some expressions that would + ;; return NaN anyway. Shouldn't be a problem. + (memq (char-before) '(?, ?} ?{))))))) (defun js2-continued-expression-p () "Return non-nil if the current line continues an expression." (save-excursion (back-to-indentation) - (or (js2-looking-at-operator-p) - (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) - (not (looking-at "\\*\\|\\+\\+\\|--\\|/[/*]"))))))) + (if (js2-looking-at-operator-p) + (or (not (memq (char-after) '(?- ?+))) + (progn + (forward-comment (- (point))) + (not (memq (char-before) '(?, ?\[ ?\())))) + (forward-comment (- (point))) + (or (bobp) (backward-char)) + (when (js2-looking-at-operator-p) + (backward-char) + (not (looking-at "\\*\\|\\+\\+\\|--\\|/[/*]")))))) (defun js2-end-of-do-while-loop-p () "Return non-nil if word after point is `while' of a do-while @@@ -431,7 -441,7 +441,7 @@@ indentation is aligned to that column. (goto-char bracket) (cond ((looking-at "[({[][ \t]*\\(/[/*]\\|$\\)") - (when (save-excursion (skip-chars-backward " \t)") + (when (save-excursion (skip-chars-backward " \t\n)") (looking-at ")")) (backward-list)) (back-to-indentation) @@@ -484,6 -494,215 +494,215 @@@ (when (cl-plusp offset) (forward-char offset))))) + ;;; JSX Indentation + + ;; The following JSX indentation code is copied basically verbatim from js.el at + ;; 958da7f, except that the prefixes on the functions/variables are changed. + + (defsubst js2--jsx-find-before-tag () + "Find where JSX starts. + + Assume JSX appears in the following instances: + - Inside parentheses, when returned or as the first argument + to a function, and after a newline + - When assigned to variables or object properties, but only + on a single line + - As the N+1th argument to a function + + This is an optimized version of (re-search-backward \"[(,]\n\" + nil t), except set point to the end of the match. This logic + executes up to the number of lines in the file, so it should be + really fast to reduce that impact." + (let (pos) + (while (and (> (point) (point-min)) + (not (progn + (end-of-line 0) + (when (or (eq (char-before) 40) ; ( + (eq (char-before) 44)) ; , + (setq pos (1- (point)))))))) + pos)) + + (defconst js2--jsx-end-tag-re + (concat "\\|/>") + "Find the end of a JSX element.") + + (defconst js2--jsx-after-tag-re "[),]" + "Find where JSX ends. + This complements the assumption of where JSX appears from + `js--jsx-before-tag-re', which see.") + + (defun js2--jsx-indented-element-p () + "Determine if/how the current line should be indented as JSX. + + Return `first' for the first JSXElement on its own line. + Return `nth' for subsequent lines of the first JSXElement. + Return `expression' for an embedded JS expression. + Return `after' for anything after the last JSXElement. + Return nil for non-JSX lines. + + Currently, JSX indentation supports the following styles: + + - Single-line elements (indented like normal JS): + + var element =
; + + - Multi-line elements (enclosed in parentheses): + + function () { + return ( +
+
+
+ ); + } + + - Function arguments: + + React.render( +
, + document.querySelector('.root') + );" + (let ((current-pos (point)) + (current-line (line-number-at-pos)) + last-pos + before-tag-pos before-tag-line + tag-start-pos tag-start-line + tag-end-pos tag-end-line + after-tag-line + parens paren type) + (save-excursion + (and + ;; Determine if we're inside a jsx element + (progn + (end-of-line) + (while (and (not tag-start-pos) + (setq last-pos (js2--jsx-find-before-tag))) + (while (forward-comment 1)) + (when (= (char-after) 60) ; < + (setq before-tag-pos last-pos + tag-start-pos (point))) + (goto-char last-pos)) + tag-start-pos) + (progn + (setq before-tag-line (line-number-at-pos before-tag-pos) + tag-start-line (line-number-at-pos tag-start-pos)) + (and + ;; A "before" line which also starts an element begins with js, so + ;; indent it like js + (> current-line before-tag-line) + ;; Only indent the jsx lines like jsx + (>= current-line tag-start-line))) + (cond + ;; Analyze bounds if there are any + ((progn + (while (and (not tag-end-pos) + (setq last-pos (re-search-forward js2--jsx-end-tag-re nil t))) + (while (forward-comment 1)) + (when (looking-at js2--jsx-after-tag-re) + (setq tag-end-pos last-pos))) + tag-end-pos) + (setq tag-end-line (line-number-at-pos tag-end-pos) + after-tag-line (line-number-at-pos after-tag-line)) + (or (and + ;; Ensure we're actually within the bounds of the jsx + (<= current-line tag-end-line) + ;; An "after" line which does not end an element begins with + ;; js, so indent it like js + (<= current-line after-tag-line)) + (and + ;; Handle another case where there could be e.g. comments after + ;; the element + (> current-line tag-end-line) + (< current-line after-tag-line) + (setq type 'after)))) + ;; They may not be any bounds (yet) + (t)) + ;; Check if we're inside an embedded multi-line js expression + (cond + ((not type) + (goto-char current-pos) + (end-of-line) + (setq parens (nth 9 (syntax-ppss))) + (while (and parens (not type)) + (setq paren (car parens)) + (cond + ((and (>= paren tag-start-pos) + ;; Curly bracket indicates the start of an embedded expression + (= (char-after paren) 123) ; { + ;; The first line of the expression is indented like sgml + (> current-line (line-number-at-pos paren)) + ;; Check if within a closing curly bracket (if any) + ;; (exclusive, as the closing bracket is indented like sgml) + (cond + ((progn + (goto-char paren) + (ignore-errors (let (forward-sexp-function) + (forward-sexp)))) + (< current-line (line-number-at-pos))) + (t))) + ;; Indicate this guy will be indented specially + (setq type 'expression)) + (t (setq parens (cdr parens))))) + t) + (t)) + (cond + (type) + ;; Indent the first jsx thing like js so we can indent future jsx things + ;; like sgml relative to the first thing + ((= current-line tag-start-line) 'first) + ('nth)))))) + + (defmacro js2--as-sgml (&rest body) + "Execute BODY as if in sgml-mode." + `(with-syntax-table sgml-mode-syntax-table + (let (forward-sexp-function + parse-sexp-lookup-properties) + ,@body))) + + (defun js2--expression-in-sgml-indent-line () + "Indent the current line as JavaScript or SGML (whichever is farther)." + (let* (indent-col + (savep (point)) + ;; Don't whine about errors/warnings when we're indenting. + ;; This has to be set before calling parse-partial-sexp below. + (inhibit-point-motion-hooks t) + (parse-status (save-excursion + (syntax-ppss (point-at-bol))))) + ;; Don't touch multiline strings. + (unless (nth 3 parse-status) + (setq indent-col (save-excursion + (back-to-indentation) + (if (>= (point) savep) (setq savep nil)) + (js2--as-sgml (sgml-calculate-indent)))) + (if (null indent-col) + 'noindent + ;; Use whichever indentation column is greater, such that the sgml + ;; column is effectively a minimum + (setq indent-col (max (js2-proper-indentation parse-status) + (+ indent-col js2-basic-offset))) + (if savep + (save-excursion (indent-line-to indent-col)) + (indent-line-to indent-col)))))) + + (defun js2-jsx-indent-line () + "Indent the current line as JSX (with SGML offsets). + i.e., customize JSX element indentation with `sgml-basic-offset' + et al." + (interactive) + (let ((indentation-type (js2--jsx-indented-element-p))) + (cond + ((eq indentation-type 'expression) + (js2--expression-in-sgml-indent-line)) + ((or (eq indentation-type 'first) + (eq indentation-type 'after)) + ;; Don't treat this first thing as a continued expression (often a "<" or + ;; ">" causes this misinterpretation) + (cl-letf (((symbol-function #'js2-continued-expression-p) 'ignore)) + (js2-indent-line))) + ((eq indentation-type 'nth) + (js2--as-sgml (sgml-indent-line))) + (t (js2-indent-line))))) + (provide 'js2-old-indent) ;;; js2-old-indent.el ends here diff --combined packages/js2-mode/tests/externs.el index 40957f600,75b93cdaf..75b93cdaf --- a/packages/js2-mode/tests/externs.el +++ b/packages/js2-mode/tests/externs.el @@@ -1,6 -1,6 +1,6 @@@ ;;; tests/externs.el --- Some tests for js2-mode. - ;; Copyright (C) 2009, 2011-2013 Free Software Foundation, Inc. + ;; Copyright (C) 2009, 2011-2014, 2016 Free Software Foundation, Inc. ;; This file is part of GNU Emacs. @@@ -42,6 -42,13 +42,13 @@@ (should (equal (js2-get-jslint-globals) '("quux" "tee" "$"))))) + (ert-deftest js2-finds-jslint-globals-with-space () + (with-temp-buffer + (insert "/* global foo, bar:false, baz:true") + (js2-mode) + (should (equal (js2-get-jslint-globals) + '("foo" "bar" "baz"))))) + ;;;TODO ;; ensure that any symbols bound with the import syntax are added to the extern list ;; ensure that any symbols bound with the export syntax exist in the file scope diff --combined packages/js2-mode/tests/indent.el index 27b6c5a0c,0fabe9563..0fabe9563 --- a/packages/js2-mode/tests/indent.el +++ b/packages/js2-mode/tests/indent.el @@@ -1,6 -1,6 +1,6 @@@ ;;; tests/indent.el --- Some tests for js2-mode. - ;; Copyright (C) 2009, 2011-2013 Free Software Foundation, Inc. + ;; Copyright (C) 2009, 2011-2016 Free Software Foundation, Inc. ;; This file is part of GNU Emacs. @@@ -31,14 -31,16 +31,16 @@@ (if keep-indent s (replace-regexp-in-string "^ *" "" s))) - (js2-mode) + (js2-jsx-mode) + (js2-reparse) ; solely for js2-jsx-self-closing, for some reason (indent-region (point-min) (point-max)) (should (string= s (buffer-substring-no-properties (point-min) (point))))))) (cl-defmacro js2-deftest-indent (name content &key bind keep-indent) `(ert-deftest ,(intern (format "js2-%s" name)) () - (let ,(append '((js2-basic-offset 2) + (let ,(append '(indent-tabs-mode + (js2-basic-offset 2) (js2-pretty-multiline-declarations t) (inhibit-point-motion-hooks t)) bind) @@@ -140,3 -142,146 +142,146 @@@ | , | bar = 8;" :bind ((js2-pretty-multiline-declarations 'dynamic))) + + (js2-deftest-indent indent-generator-method + "class A { + | * x() { + | return 1 + | * a(2); + | } + |}") + + (js2-deftest-indent indent-generator-computed-method + "class A { + | *[Symbol.iterator]() { + | yield 'Foo'; + | yield 'Bar'; + | } + |}") + + (js2-deftest-indent case-inside-switch + "switch(true) { + |case 'true': + | return 1; + |}") + + (js2-deftest-indent case-inside-switch-with-extra-indent + "switch(true) { + | case 'true': + | return 1; + |}" + :bind ((js2-indent-switch-body t))) + + (js2-deftest-indent case-inside-switch-with-extra-indent-curly-after-newline + "switch(true) + |{ + | case 'true': + | return 1; + |}" + :bind ((js2-indent-switch-body t))) + + (js2-deftest-indent continued-expression-vs-unary-minus + "var arr = [ + | -1, 2, + | -3, 4 + + | -5 + |];") + + (js2-deftest-indent spread-inside-array + "var z = [ + | ...iterableObj, + | 4, + | 5 + |]") + + (js2-deftest-indent jsx-one-line + "var foo =
;") + + (js2-deftest-indent jsx-children-parentheses + "return ( + |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |);") + + (js2-deftest-indent jsx-children-unclosed + "return ( + |
+ |
") + + (js2-deftest-indent jsx-argument + "React.render( + |
+ |
+ |
, + | { + | a: 1 + | }, + |
+ |
+ |
+ |);") + + (js2-deftest-indent jsx-leading-comment + "return ( + | // Sneaky! + |
+ |);") + + (js2-deftest-indent jsx-trailing-comment + "return ( + |
+ | // Sneaky! + |);") + + (js2-deftest-indent jsx-self-closing + ;; This ensures we know the bounds of a self-closing element + "React.render( + | , + | { + | a: 1 + | } + |);" + :bind ((sgml-attribute-offset 1))) ; Emacs 24.5 -> 25 compat + + (js2-deftest-indent jsx-embedded-js-content + "return ( + |
+ | {array.map(function () { + | return { + | a: 1 + | }; + | })} + |
+ |);") + + (js2-deftest-indent jsx-embedded-js-unclosed + "return ( + |
+ | {array.map(function () { + | return { + | a: 1") + + (js2-deftest-indent jsx-embedded-js-attribute + "return ( + |
+ |
+ |);") diff --combined packages/js2-mode/tests/json-path.el index 000000000,70aecefb1..70aecefb1 mode 000000,100644..100644 --- a/packages/js2-mode/tests/json-path.el +++ b/packages/js2-mode/tests/json-path.el @@@ -1,0 -1,64 +1,64 @@@ + ;;; tests/json-path.el --- Test of using js2-mode AST to print JSON path. + + ;; Copyright (C) 2015 Free Software Foundation, Inc. + + ;; This file is part of GNU Emacs. + + ;; GNU Emacs is free software: you can redistribute it and/or modify + ;; it under the terms of the GNU General Public License as published by + ;; the Free Software Foundation, either version 3 of the License, or + ;; (at your option) any later version. + + ;; GNU Emacs is distributed in the hope that it will be useful, + ;; but WITHOUT ANY WARRANTY; without even the implied warranty of + ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ;; GNU General Public License for more details. + + ;; You should have received a copy of the GNU General Public License + ;; along with GNU Emacs. If not, see . + + ;;; Code: + + (require 'ert) + (require 'js2-mode) + + (ert-deftest js2-json-path-with-actual-array-index () + (with-temp-buffer + (insert "var a = { hello: [1, 2, [1,3,3,4, { world: { hell: { yes: [1,2, 'test'] } } }]] };") + (js2-mode) + (goto-char 0) + (search-forward "test") + (should (string= (js2-print-json-path) "hello[2][4].world.hell.yes[2]")))) + + (ert-deftest js2-json-path-pure-arrays () + (with-temp-buffer + (insert "var a = [5, 1, 4, [ 4, [1, 2, [1, 3.9, 4, [1, 2, 'test',3]]]], 9, 9];") + (js2-mode) + (goto-char 0) + (search-forward "test") + (should (string= (js2-print-json-path) "[3][1][2][3][2]")))) + + (ert-deftest js2-json-path-key-is-numeric () + (with-temp-buffer + (insert "var b = {hello: {3 : {world: {2: 'test'}}}};") + (js2-mode) + (goto-char 0) + (search-forward "test") + (should (string= (js2-print-json-path) "hello.3.world.2")))) + + (ert-deftest js2-json-path-not-found () + (with-temp-buffer + (insert "console.log('test');") + (js2-mode) + (goto-char 0) + (search-forward "test") + (should (eq (js2-print-json-path) nil)))) + + (ert-deftest js2-json-path-with-array-index-hardcoded () + (with-temp-buffer + (insert "var a = { hello: [1, 2, [1,3,3,4, { world: { hell: { yes: [1,2, 'test'] } } }]] };") + (js2-mode) + (goto-char 0) + (search-forward "test") + (should (string= (js2-print-json-path 1) "hello[1][1].world.hell.yes[1]")) + (should (string= (js2-print-json-path 0) "hello[0][0].world.hell.yes[0]")))) diff --combined packages/js2-mode/tests/parser.el index dc8c001ca,b4aa68337..b4aa68337 --- a/packages/js2-mode/tests/parser.el +++ b/packages/js2-mode/tests/parser.el @@@ -1,6 -1,6 +1,6 @@@ ;;; tests/parser.el --- Some tests for js2-mode. - ;; Copyright (C) 2009, 2011-2013 Free Software Foundation, Inc. + ;; Copyright (C) 2009, 2011-2016 Free Software Foundation, Inc. ;; This file is part of GNU Emacs. @@@ -35,14 -35,18 +35,18 @@@ ,@body) (fundamental-mode))))) + (defun js2-mode--and-parse () + (js2-mode) + (js2-reparse)) + (defun js2-test-string-to-ast (s) (insert s) - (js2-mode) + (js2-mode--and-parse) (should (null js2-mode-buffer-dirty-p)) js2-mode-ast) (cl-defun js2-test-parse-string (code-string &key syntax-error errors-count - reference) + reference warnings-count) (ert-with-test-buffer (:name 'origin) (let ((ast (js2-test-string-to-ast code-string))) (if syntax-error @@@ -57,10 -61,13 +61,13 @@@ (skip-chars-backward " \t\n") (should (string= (or reference code-string) (buffer-substring-no-properties - (point-min) (point))))))))) + (point-min) (point))))) + (when warnings-count + (should (= warnings-count + (length (js2-ast-root-warnings ast))))))))) (cl-defmacro js2-deftest-parse (name code-string &key bind syntax-error errors-count - reference) + reference warnings-count) "Parse CODE-STRING. If SYNTAX-ERROR is nil, print syntax tree with `js2-print-tree' and assert the result to be equal to REFERENCE, if present, or the original string. If SYNTAX-ERROR @@@ -73,6 -80,7 +80,7 @@@ the test. (js2-test-parse-string ,code-string :syntax-error ,syntax-error :errors-count ,errors-count + :warnings-count ,warnings-count :reference ,reference)))) ;;; Basics @@@ -126,6 -134,9 +134,9 @@@ (js2-deftest-parse let-expression-statement "let (x = 42) x;") + (js2-deftest-parse void + "void 0;") + ;;; Callers of `js2-valid-prop-name-token' (js2-deftest-parse parse-property-access-when-not-keyword @@@ -158,6 -169,12 +169,12 @@@ (js2-deftest-parse parse-for-of "for (var a of []) {\n}") + (js2-deftest-parse of-can-be-name + "void of;") + + (js2-deftest-parse of-can-be-object-name + "of.z;") + (js2-deftest-parse of-can-be-var-name "var of = 3;") @@@ -167,16 -184,49 +184,49 @@@ ;;; Destructuring binding (js2-deftest-parse destruct-in-declaration - "var {a, b} = {a: 1, b: 2};") + "var {a, b} = {a: 1, b: 2};" + :warnings-count 0) (js2-deftest-parse destruct-in-arguments - "function f({a: aa, b: bb}) {\n}") + "function f({a: aa, b: bb}) {\n}" + :warnings-count 0) (js2-deftest-parse destruct-in-array-comp-loop "[a + b for ([a, b] in [[0, 1], [1, 2]])];") (js2-deftest-parse destruct-in-catch-clause - "try {\n} catch ({a, b}) {\n a + b;\n}") + "try {\n} catch ({a, b}) {\n a + b;\n}" + :warnings-count 0) + + (js2-deftest-parse destruct-with-initializer-in-object + "var {a, b = 2, c} = {};\nb;" + :warnings-count 0) + + (js2-deftest-parse destruct-with-initializer-in-array + "var [a, b = 2, c] = [];\nb;" + :warnings-count 0) + + (js2-deftest-parse destruct-non-name-target-is-error + "var {1=1} = {};" :syntax-error "1" :errors-count 1) + + (js2-deftest-parse destruct-with-initializer-in-function-params + "function f({a, b = 1, c}, [d, e = 1, f]) {\n}") + + (js2-deftest-parse destruct-with-default-in-function-params + "function f({x = 1, y = 2} = {}, [x, y] = [1, 2]) {\n}") + + (js2-deftest-parse destruct-name-conflict-is-error-in-object + "\"use strict\";\nvar {a=1,a=2} = {};" :syntax-error "a" :errors-count 1) + + (js2-deftest destruct-name-conflict-is-warning-in-array "\"use strict\";\nvar [a=1,a=2] = [];" + (js2-mode--and-parse) + (should (equal '("msg.var.redecl" "a") + (caar js2-parsed-warnings)))) + + (js2-deftest initializer-outside-destruct-is-error "({a=1});" + (js2-mode--and-parse) + (should (equal "msg.init.no.destruct" + (car (caar js2-parsed-errors))))) ;;; Object literals @@@ -189,6 -239,11 +239,11 @@@ (js2-deftest-parse object-literal-method "var x = {f(y) { return y;\n}};") + (js2-deftest object-literal-method-own-name-in-scope "({f(){f();}});" + (js2-mode--and-parse) + (should (equal '("msg.undeclared.variable" "f") + (caar js2-parsed-warnings)))) + (js2-deftest-parse object-literal-getter-method "var x = {get f() { return 42;\n}};") @@@ -205,7 -260,7 +260,7 @@@ "var x = {get [foo + bar]() { return 42;\n}};") (js2-deftest-parse object-literal-generator - "var x = {*foo() { yield 42;\n}};") + "var x = {*foo() { yield* 42;\n}};") (js2-deftest-parse object-literal-computed-generator-key "var x = {*[foo + bar]() { yield 42;\n}};") @@@ -213,11 -268,11 +268,11 @@@ ;;; Function definition (js2-deftest function-redeclaring-var "var gen = 3; function gen() {};" - (js2-mode) + (js2-mode--and-parse) (should (= (length (js2-ast-root-warnings js2-mode-ast)) 1))) (js2-deftest function-expression-var-same-name "var gen = function gen() {};" - (js2-mode) + (js2-mode--and-parse) (should (null (js2-ast-root-warnings js2-mode-ast)))) ;;; Function parameters @@@ -290,14 -345,29 +345,29 @@@ "'use strict';\nvar number = 0644;" :syntax-error "0644" :errors-count 1) + (js2-deftest-parse function-strict-octal-allow-0o + "'use strict';\n0o644;" :reference "'use strict';\n420;") + (js2-deftest-parse function-strict-duplicate-keys "'use strict';\nvar object = {a: 1, a: 2, 'a': 3, ['a']: 4, 1: 5, '1': 6, [1 + 1]: 7};" :syntax-error "a" :errors-count 4) ; "a" has 3 dupes, "1" has 1 dupe. - ;; errors... or lackthereof. + (js2-deftest-parse function-strict-duplicate-getter + "'use strict';\nvar a = {get x() {}, get x() {}};" + :syntax-error "x" :errors-count 1) + + (js2-deftest-parse function-strict-duplicate-setter + "'use strict';\nvar a = {set x() {}, set x() {}};" + :syntax-error "x" :errors-count 1) + + ;;; Lack of errors in strict mode + (js2-deftest-parse function-strict-const-scope "'use strict';\nconst a;\nif (1) {\n const a;\n}") + (js2-deftest-parse function-strict-no-getter-setter-duplicate + "'use strict';\nvar a = {get x() {}, set x() {}};") + ;;; Spread operator (js2-deftest-parse spread-in-array-literal @@@ -306,6 -376,26 +376,26 @@@ (js2-deftest-parse spread-in-function-call "f(3, ...[t(2), t(3)], 42, ...[t(4)]);") + (js2-deftest-parse rest-in-array-destructure + "let [x, y, z, ...w] = [1, ...a, ...b, c];") + + (js2-deftest-parse comma-after-rest-in-array + "let [...x,] = [1, 2, 3];" + :syntax-error "," :errors-count 1) + + (js2-deftest-parse elem-after-rest-in-array + "let [...x, y] = [1, 2, 3];" + :syntax-error "," :errors-count 2) + + (js2-deftest-parse array-destructure-expr-default + "let [[x] = [3]] = y;") + + (js2-deftest-parse spread-in-object-literal + "f({x, y, ...z});") + + (js2-deftest-parse rest-in-object-literal + "const {x, y, ...z} = f();") + ;;; Arrow functions (js2-deftest-parse arrow-function-with-empty-args-and-no-curlies @@@ -344,7 -434,7 +434,7 @@@ (js2-deftest no-label-node-inside-expr "x = y:" (let (js2-parse-interruptable-p) - (js2-mode)) + (js2-mode--and-parse)) (let ((assignment (js2-expr-stmt-node-expr (car (js2-scope-kids js2-mode-ast))))) (should (js2-name-node-p (js2-assign-node-right assignment))))) @@@ -387,6 -477,79 +477,79 @@@ (js2-deftest-parse parse-generator-comp-with-yield-inside-function-is-ok "(for (x of []) function*() { yield x;\n});") + ;;; Async + + (js2-deftest-parse async-function-statement + "async function foo() {\n}") + + (js2-deftest-parse async-function-statement-inside-block + "if (true) {\n async function foo() {\n }\n}") + + (js2-deftest-parse async-function-expression-statements-are-verboten + "async function() {}" :syntax-error "(") + + (js2-deftest-parse async-named-function-expression + "a = async function b() {};") + + (js2-deftest-parse async-arrow-function-expression + "a = async (b) => { b;\n};") + + (js2-deftest-parse async-method-in-object-literal + "({async f() {}});") + + (js2-deftest-parse async-method-in-class-body + "class C {\n async foo() {}\n}") + + (js2-deftest-parse static-async-method-in-class-body + "class C {\n static async foo() {}\n}") + + (js2-deftest-parse async-method-allow-await + "({async f() { await x;\n}});") + + ;;; Await + + (js2-deftest-parse await-is-ok "async function foo() {\n await bar();\n}") + + (js2-deftest-parse await-inside-assignment-is-ok + "async function foo() {\n var result = await bar();\n}") + + (js2-deftest-parse await-inside-array-is-ok + "async function foo() {\n var results = [await bar(), await baz()];\n}") + + (js2-deftest-parse await-inside-non-async-function-is-not-ok + "function foo() {\n await bar();\n}" + :syntax-error "await") + + (js2-deftest-parse await-inside-non-async-arrow-function-is-not-ok + "a = () => { await bar();\n}" + :syntax-error "await") + + ;;; 'async' and 'await' are contextual keywords + + (js2-deftest-parse async-can-be-name + "void async;") + + (js2-deftest-parse async-can-be-object-name + "async.z;") + + (js2-deftest-parse async-can-be-var-name + "var async = 3;") + + (js2-deftest-parse async-can-be-function-name + "function async() {\n}") + + (js2-deftest-parse await-can-be-name + "void await;") + + (js2-deftest-parse await-can-be-object-name + "await.z;") + + (js2-deftest-parse await-can-be-var-name + "var await = 3;") + + (js2-deftest-parse await-can-be-function-name + "function await() {\n}") + ;;; Numbers (js2-deftest-parse decimal-starting-with-zero "081;" :reference "81;") @@@ -639,7 -802,7 +802,7 @@@ (should export-node) (should (js2-var-decl-node-p (js2-export-node-declaration export-node))))) - (js2-deftest export-class-declaration "export class Foo {};" + (js2-deftest export-class-declaration "export class Foo {}" (js2-init-scanner) (js2-push-scope (make-js2-scope :pos 0)) (should (js2-match-token js2-EXPORT)) @@@ -647,7 -810,7 +810,7 @@@ (should export-node) (should (js2-class-node-p (js2-export-node-declaration export-node))))) - (js2-deftest export-function-declaration "export default function doStuff() {};" + (js2-deftest export-function-declaration "export default function doStuff() {}" (js2-init-scanner) (js2-push-scope (make-js2-scope :pos 0)) (should (js2-match-token js2-EXPORT)) @@@ -655,7 -818,7 +818,7 @@@ (should export-node) (should (js2-export-node-default export-node)))) - (js2-deftest export-generator-declaration "export default function* one() {};" + (js2-deftest export-generator-declaration "export default function* one() {}" (js2-init-scanner) (js2-push-scope (make-js2-scope :pos 0)) (should (js2-match-token js2-EXPORT)) @@@ -672,24 -835,43 +835,43 @@@ (should (js2-export-node-default export-node)))) (js2-deftest export-function-no-semicolon "export default function foo() {}" - (js2-mode) + (js2-mode--and-parse) (should (equal nil js2-parsed-warnings))) (js2-deftest export-default-function-no-semicolon "export function foo() {}" - (js2-mode) + (js2-mode--and-parse) (should (equal nil js2-parsed-warnings))) (js2-deftest export-anything-else-does-require-a-semicolon "export var obj = {}" - (js2-mode) + (js2-mode--and-parse) (should (not (equal nil js2-parsed-warnings)))) + (js2-deftest export-default-async-function-no-semicolon "export default async function foo() {}" + (js2-mode--and-parse) + (should (equal nil js2-parsed-warnings))) + (js2-deftest export-async-function-no-semicolon "export async function foo() {}" + (js2-mode--and-parse) + (should (equal nil js2-parsed-warnings))) + (js2-deftest-parse parse-export-rexport "export * from 'other/lib';") (js2-deftest-parse parse-export-export-named-list "export {foo, bar as bang};") (js2-deftest-parse parse-re-export-named-list "export {foo, bar as bang} from 'other/lib';") (js2-deftest-parse parse-export-const-declaration "export const PI = Math.PI;") (js2-deftest-parse parse-export-let-declaration "export let foo = [1];") - (js2-deftest-parse parse-export-function-declaration "export default function doStuff() {};") - (js2-deftest-parse parse-export-generator-declaration "export default function* one() {};") + (js2-deftest-parse parse-export-function-declaration "export default function doStuff() {\n}") + (js2-deftest-parse parse-export-generator-declaration "export default function* one() {\n}") (js2-deftest-parse parse-export-assignment-expression "export default a = b;") + (js2-deftest-parse parse-export-function-declaration-no-semi + "export function f() {\n}") + + (js2-deftest-parse parse-export-class-declaration-no-semi + "export class C {\n}") + + (js2-deftest-parse parse-export-async-function-allow-await + "export async function f() {\n await f();\n}") + + (js2-deftest-parse parse-export-default-async-function-allow-await + "export default async function f() {\n await f();\n}") + ;;; Strings (js2-deftest-parse string-literal @@@ -749,10 -931,13 +931,13 @@@ (js2-deftest-parse parse-class-keywordlike-method "class C {\n delete() {}\n if() {}\n}") + (js2-deftest-parse parse-harmony-class-allow-semicolon-element + "class Foo {;}" :reference "class Foo {\n}") + ;;; Scopes (js2-deftest ast-symbol-table-includes-fn-node "function foo() {}" - (js2-mode) + (js2-mode--and-parse) (let ((entry (js2-scope-get-symbol js2-mode-ast 'foo))) (should (= (js2-symbol-decl-type entry) js2-FUNCTION)) (should (equal (js2-symbol-name entry) "foo")) @@@ -762,7 -947,7 +947,7 @@@ function bar() {} var x; }" - (js2-mode) + (js2-mode--and-parse) (let* ((scope (js2-node-at-point (point-min))) (fn-entry (js2-scope-get-symbol scope 'bar)) (var-entry (js2-scope-get-symbol scope 'x))) @@@ -780,36 -965,26 +965,26 @@@ (should (funcall predicate (js2-get-defining-scope scope variable))))) (js2-deftest for-node-is-declaration-scope "for (let i = 0; i; ++i) {};" - (js2-mode) + (js2-mode--and-parse) (js2-test-scope-of-nth-variable-satisifies-predicate "i" 0 #'js2-for-node-p)) - (js2-deftest const-scope-sloppy-script "{const a;} a;" - (js2-mode) - (js2-test-scope-of-nth-variable-satisifies-predicate "a" 0 #'js2-script-node-p) - (js2-test-scope-of-nth-variable-satisifies-predicate "a" 1 #'js2-script-node-p)) - - (js2-deftest const-scope-strict-script "'use strict'; { const a; } a;" - (js2-mode) + (js2-deftest const-scope-inside-script "{ const a; } a;" + (js2-mode--and-parse) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 0 #'js2-block-node-p) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 1 #'null)) - (js2-deftest const-scope-sloppy-function "function f() { { const a; } a; }" - (js2-mode) - (js2-test-scope-of-nth-variable-satisifies-predicate "a" 0 #'js2-function-node-p) - (js2-test-scope-of-nth-variable-satisifies-predicate "a" 1 #'js2-function-node-p)) - - (js2-deftest const-scope-strict-function "function f() { 'use strict'; { const a; } a; }" - (js2-mode) + (js2-deftest const-scope-inside-function "function f() { { const a; } a; }" + (js2-mode--and-parse) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 0 #'js2-block-node-p) (js2-test-scope-of-nth-variable-satisifies-predicate "a" 1 #'null)) (js2-deftest array-comp-is-result-scope "[x * 2 for (x in y)];" - (js2-mode) + (js2-mode--and-parse) (js2-test-scope-of-nth-variable-satisifies-predicate "x" 0 #'js2-comp-loop-node-p)) (js2-deftest array-comp-has-parent-scope "var a,b=[for (i of [[1,2]]) for (j of i) j * a];" - (js2-mode) + (js2-mode--and-parse) (search-forward "for") (forward-char -3) (let ((node (js2-node-at-point))) @@@ -879,23 -1054,23 +1054,23 @@@ ;;; Error handling (js2-deftest for-node-with-error-len "for " - (js2-mode) + (js2-mode--and-parse) (let ((node (js2-node-at-point (point-min)))) (should (= (js2-node-len (js2-node-parent node)) 4)))) (js2-deftest function-without-parens-error "function b {}" ;; Should finish the parse. - (js2-mode)) + (js2-mode--and-parse)) ;;; Comments (js2-deftest comment-node-length "//" - (js2-mode) + (js2-mode--and-parse) (let ((node (js2-node-at-point (point-min)))) (should (= (js2-node-len node) 2)))) (js2-deftest comment-node-length-newline "//\n" - (js2-mode) + (js2-mode--and-parse) (let ((node (js2-node-at-point (point-min)))) (should (= (js2-node-len node) 3)))) @@@ -934,7 -1109,7 +1109,7 @@@ (insert ,buffer-contents)) (unwind-protect (progn - (js2-mode) + (js2-mode--and-parse) (should (equal ,summary (js2--variables-summary (js2--classify-variables))))) (fundamental-mode)))))