X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/e7f3aa420da097fd3f2ffe6c2ba5aa42af4b671f..f10533854f4c7bb54247a11981191bf37b70cb36:/packages/js2-mode/js2-mode.el diff --git a/packages/js2-mode/js2-mode.el b/packages/js2-mode/js2-mode.el index 2d6f33685..5ccfbccf1 100644 --- a/packages/js2-mode/js2-mode.el +++ b/packages/js2-mode/js2-mode.el @@ -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 @@ ;; (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 @@ (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 @@ Set `js2-include-rhino-externs' to t to include them.") (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.") @@ -260,7 +267,7 @@ If `js2-dynamic-idle-timer-adjust' is 0 or negative, (defcustom js2-concat-multiline-strings t "When non-nil, `js2-line-break' in mid-string will make it a -string concatenation. When `eol', the '+' will be inserted at the +string concatenation. When `eol', the `+' will be inserted at the end of the line, otherwise, at the beginning of the next line." :type '(choice (const t) (const eol) (const nil)) :group 'js2-mode) @@ -270,6 +277,11 @@ end of the line, otherwise, at the beginning of the next line." :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, @@ -308,10 +320,10 @@ interferes with type inference (in systems that support it.)" :group 'js2-mode) (defcustom js2-strict-cond-assign-warning t - "Non-nil to warn about expressions like if (a = b). -This often should have been '==' instead of '='. If the warning + "Non-nil to warn about usage like `if (a = b)'. +This often should have been `==' instead of `='. If the warning is enabled, you can suppress it on a per-expression basis by -parenthesizing the expression, e.g. if ((a = b)) ..." +parenthesizing the expression, e.g., `if ((a = b)) ...'." :type 'boolean :group 'js2-mode) @@ -385,7 +397,7 @@ setting `allowMemberExprAsFunctionName'. The experimental syntax is: function ( [ arg-list ] ) { } -Where member-expr is a non-parenthesized 'member expression', which +Where member-expr is a non-parenthesized `member expression', which is anything at the grammar level of a new-expression or lower, meaning any expression that does not involve infix or unary operators. @@ -610,7 +622,9 @@ which doesn't seem particularly useful, but Rhino permits it." (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 @@ List of chars built up while scanning various tokens.") (string "") number number-base + number-legacy-octal-p regexp-flags comment-type follows-eol-p) @@ -1018,6 +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 @@ 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 @@ the correct number of ARGS must be provided." (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 @@ the correct number of ARGS must be provided." "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 @@ the correct number of ARGS must be provided." (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 @@ 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) @@ -2717,7 +2747,7 @@ It contains a local-name node which is the name of the value in the current scope, and extern-name which is the name of the value in the imported or exported scope. By default these are the same, but if the name is aliased as in {foo as bar}, it would have an extern-name node -containing 'foo' and a local-name node containing 'bar'." +containing `foo' and a local-name node containing `bar'." local-name ; js2-name-node with the variable name in this scope extern-name) ; js2-name-node with the value name in the exporting module @@ -2735,8 +2765,8 @@ different, visit the extern-name." (js2-visit-ast extern-name v)))) (defun js2-print-extern-binding (n _i) - "Print a representation of a single extern binding. E.g. 'foo' or -'foo as bar'." + "Print a representation of a single extern binding. E.g. `foo' or +`foo as bar'." (let ((local-name (js2-export-binding-node-local-name n)) (extern-name (js2-export-binding-node-extern-name n))) (insert (js2-name-node-name extern-name)) @@ -2864,9 +2894,9 @@ local context." len name))) "AST node for a complete namespace import. -E.g. the '* as lib' expression in: +E.g. the `* as lib' expression in: -import * as lib from 'src/lib' +import * as lib from \\='src/lib\\=' It contains a single name node referring to the bound name." name) ; js2-name-node of the bound name. @@ -2886,7 +2916,7 @@ It contains a single name node referring to the bound name." module-id metadata-p))) "AST node for the from clause in an import or export statement. -E.g. from 'my/module'. It can refere to either an external module, or to the +E.g. from \\='my/module\\='. It can refere to either an external module, or to the modules metadata itself." module-id ; string containing the module specifier. metadata-p) ; true if this clause refers to the module's metadata @@ -3195,7 +3225,7 @@ NODE is a `js2-labels-node'. LABEL is an identifier." len label target))) "AST node for a break statement. The label field is a `js2-name-node', possibly nil, for the named label -if provided. E.g. in 'break foo', it represents 'foo'. The target field +if provided. E.g. in `break foo', it represents `foo'. The target field is the target of the break - a label node or enclosing loop/switch statement.") (put 'cl-struct-js2-break-node 'js2-visitor 'js2-visit-jump-node) @@ -3241,6 +3271,7 @@ a `js2-label-node' or the innermost enclosing loop.") 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 @@ The `params' field is a Lisp list of nodes. Each node is either a simple 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 @@ The `params' field is a Lisp list of nodes. Each node is either a simple (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 @@ The `params' field is a Lisp list of nodes. Each node is either a simple (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 @@ The type field inherited from `js2-node' holds the operator." (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 @@ The type field holds the actual assignment operator.") 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 @@ property is added if the operator follows the operand." (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 @@ Returns 0 if NODE is nil or its identifier field is nil." (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 @@ You can tell the quote type by looking at the first character." (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 @@ 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 @@ optional `js2-expr-node'" (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 @@ 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 @@ -4251,7 +4304,7 @@ no explicit target, which is valid in certain expression contexts such as company..employee.(@id < 100) -in this case, the @id is a `js2-xml-ref' that is part of an infix '<' +in this case, the @id is a `js2-xml-ref' that is part of an infix `<' expression whose parent is a `js2-xml-dot-query-node'." namespace at-pos @@ -4273,7 +4326,7 @@ expression whose parent is a `js2-xml-dot-query-node'." "AST node for an E4X XML [expr] property-ref expression. The JavaScript syntax is an optional @, an optional ns::, and a name. - [ '@' ] [ name '::' ] name + [ `@' ] [ name `::' ] name Examples include name, ns::name, ns::*, *::name, *::*, @attr, @ns::attr, @ns::*, @*::attr, @*::*, and @*. @@ -4312,7 +4365,7 @@ expression." "AST node for an E4X XML [expr] member-ref expression. Syntax: - [ '@' ] [ name '::' ] '[' expr ']' + [ `@' ] [ name `::' ] `[' expr `]' Examples include ns::[expr], @ns::[expr], @[expr], *::[expr] and @*::[expr]. @@ -4483,7 +4536,7 @@ For a simple name, the kids list has exactly one node, a `js2-name-node'." (pos js2-ts-cursor) len name value eq-pos quote-type))) - "AST node representing a foo='bar' XML attribute value. Not yet used." + "AST node representing a foo=\\='bar\\=' XML attribute value. Not yet used." name ; a `js2-xml-name-node' value ; a `js2-xml-name-node' eq-pos ; buffer position of "=" sign @@ -5106,6 +5159,8 @@ You should use `js2-print-tree' instead of this function." (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) @@ -5419,7 +5474,7 @@ Returns logical OR of END_* flags" (defun js2-always-defined-boolean-p (node) "Check if NODE always evaluates to true or false in boolean context. -Returns 'ALWAYS_TRUE, 'ALWAYS_FALSE, or nil if it's neither always true +Returns `ALWAYS_TRUE', `ALWAYS_FALSE', or nil if it's neither always true nor always false." (let ((tt (js2-node-type node)) num) @@ -5474,7 +5529,7 @@ Signals an error if it's not a recognized token." (error "Invalid token: %s" code))))) (defsubst js2-tt-sym (tok) - "Return symbol for TOK given its code, e.g. 'js2-LP for code 86." + "Return symbol for TOK given its code, e.g. `js2-LP' for code 86." (intern (js2-tt-name tok))) (defconst js2-token-codes @@ -5489,7 +5544,7 @@ Signals an error if it's not a recognized token." "Hashtable mapping token type symbols to their bytecodes.") (defsubst js2-tt-code (sym) - "Return code for token symbol SYM, e.g. 86 for 'js2-LP." + "Return code for token symbol SYM, e.g. 86 for `js2-LP'." (or (gethash sym js2-token-codes) (error "Invalid token symbol: %s " sym))) ; signal code bug @@ -5543,7 +5598,7 @@ Also updates `js2-ts-hit-eof' and `js2-ts-line-start' as needed." (defun js2-read-unicode-escape () "Read a \\uNNNN sequence from the input. -Assumes the ?\ and ?u have already been read. +Assumes the ?\\ and ?u have already been read. Returns the unicode character, or nil if it wasn't a valid character. Doesn't change the values of any scanner variables." ;; I really wish I knew a better way to do this, but I can't @@ -5735,7 +5790,7 @@ The values are default faces to use for highlighting the keywords.") do (puthash (symbol-name k) 'js2-RESERVED table)) table) - "JavaScript reserved words by name, mapped to 'js2-RESERVED.") + "JavaScript reserved words by name, mapped to `js2-RESERVED'.") (defun js2-collect-string (buf) "Convert BUF, a list of chars, to a string. @@ -5746,7 +5801,7 @@ Reverses BUF before converting." (defun js2-string-to-keyword (s) "Return token for S, a string, if S is a keyword or reserved word. -Returns a symbol such as 'js2-BREAK, or nil if not keyword/reserved." +Returns a symbol such as `js2-BREAK', or nil if not keyword/reserved." (or (gethash s js2-keyword-names) (gethash s js2-reserved-word-names))) @@ -5855,7 +5910,7 @@ its relevant fields and puts it into `js2-ti-tokens'." (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 @@ its relevant fields and puts it into `js2-ti-tokens'." ((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 @@ its relevant fields and puts it into `js2-ti-tokens'." (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 @@ its relevant fields and puts it into `js2-ti-tokens'." (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 @@ Shown at or above `js2-highlight-level' 3.") (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 @@ of a simple name. Called before EXPR has a parent node." '("alias" "augments" "borrows" + "callback" "bug" "base" "config" "default" "define" "exception" + "func" "function" "member" "memberOf" + "method" "name" "namespace" "since" @@ -6891,6 +6954,7 @@ of a simple name. Called before EXPR has a parent node." "export" "fileoverview" "final" + "func" "function" "hidden" "ignore" @@ -6899,6 +6963,7 @@ of a simple name. Called before EXPR has a parent node." "inner" "interface" "license" + "method" "noalias" "noshadow" "notypecheck" @@ -7199,11 +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)))) @@ -7361,7 +7427,7 @@ When passed arguments of wrong type, does nothing." (js2-record-object-literal node qname (js2-node-pos node))))))))) (defun js2-compute-nested-prop-get (node) - "If NODE is of form foo.bar, foo['bar'], or any nested combination, return + "If NODE is of form foo.bar, foo[\\='bar\\='], or any nested combination, return component nodes as a list. Otherwise return nil. Element-gets are treated as property-gets if the index expression is a string, or a positive integer." (let (left right head) @@ -7395,22 +7461,23 @@ We do a depth-first traversal of NODE. For any functions we find, 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 @@ For instance, processing a nested scope requires a parent function node." (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)) @@ -7572,19 +7639,19 @@ Recurses through nodes, and for each one whose second element is a list, appends the list's flattened elements to the current element. Also changes the tails into conses. For instance, this pre-flattened trie -'(a ((b 20) + (a ((b 20) (c ((d 30) (e 40))))) becomes -'(a (b . 20) + (a (b . 20) (c (d . 30) (e . 40))) Note that the root of the trie has no key, just a list of chains. This is also true for the value of any key with multiple children, -e.g. key 'c' in the example above." +e.g. key `c' in the example above." (cond ((listp (car trie)) (mapcar #'js2-flatten-trie trie)) @@ -7676,14 +7743,67 @@ Returns nil and consumes nothing if MATCH is not the next token." (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 @@ 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))) @@ -7816,11 +7933,11 @@ The returned AST root node is given some additional properties: so the value is not necessarily reliable. An optional callback CB can be specified to report parsing -progress. If `(functionp CB)' returns t, it will be called with +progress. If (functionp CB) returns t, it will be called with the current line number once before parsing begins, then again each time the lexer reaches a new line number. -CB can also be a list of the form `(symbol cb ...)' to specify +CB can also be a list of the form (symbol cb ...) to specify multiple callbacks with different criteria. Each symbol is a criterion keyword, and the following element is the callback to call @@ -7876,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 @@ declared; probably to check them for errors." (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 @@ 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 @@ represented by FN-NODE at POS." (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 @@ Last token scanned is the close-curly for the function body." (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 @@ Last token scanned is the close-curly for the function body." 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 @@ Last token scanned is the close-curly for the function body." :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 @@ Last token scanned is the close-curly for the function body." (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 @@ arrow function), NAME is js2-name-node." (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 @@ node are given relative start positions and correct lengths." (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 @@ node are given relative start positions and correct lengths." (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)) @@ -8441,11 +8584,11 @@ Return value is a list (EXPR LP RP), with absolute paren positions." "Parse the bindings in an import statement. This can take many forms: -ImportedDefaultBinding -> 'foo' -NameSpaceImport -> '* as lib' -NamedImports -> '{foo as bar, bang}' -ImportedDefaultBinding , NameSpaceImport -> 'foo, * as lib' -ImportedDefaultBinding , NamedImports -> 'foo, {bar, baz as bif}' +ImportedDefaultBinding -> `foo' +NameSpaceImport -> `* as lib' +NamedImports -> `{foo as bar, bang}' +ImportedDefaultBinding , NameSpaceImport -> `foo, * as lib' +ImportedDefaultBinding , NamedImports -> `foo, {bar, baz as bif}' Try to match namespace imports and named imports first because nothing can come after them. If it is an imported default binding, then it could have named @@ -8474,7 +8617,7 @@ imports or a namespace import that follows it. (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) @@ -8504,53 +8647,51 @@ imports or a namespace import that follows it. clause)) (defun js2-parse-namespace-import () - "Parse a namespace import expression such as '* as bar'. + "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)))) + "Parse the from clause in an import or export statement. +E.g., \"from \\='src/lib\\='\"." + (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 +8699,7 @@ The current token must be js2-MUL." 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,10 +8708,10 @@ js2-LC. Return a lisp list of js2-export-binding-node" "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 +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 js2-export-binding-node and consume all the tokens. If it does not match, it consumes no tokens." (let ((extern-name (when (js2-match-prop-name) (js2-current-token-string))) @@ -8579,45 +8720,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 () @@ -8725,7 +8870,7 @@ consumes no tokens." (defun js2-parse-export () "Parse an export statement. -The Last matched token must be js2-EXPORT. Currently, the 'default' and 'expr' +The Last matched token must be js2-EXPORT. Currently, the `default' and `expr' expressions should only be either hoistable expressions (function or generator) or assignment expressions, but there is no checking to enforce that and so it will parse without error a small subset of @@ -8745,27 +8890,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 +8973,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) @@ -9331,10 +9494,10 @@ expression and return it wrapped in a `js2-expr-stmt-node'." (defun js2-parse-variables (decl-type pos) "Parse a comma-separated list of variable declarations. -Could be a 'var', 'const' or 'let' expression, possibly in a for-loop initializer. +Could be a `var', `const' or `let' expression, possibly in a for-loop initializer. DECL-TYPE is a token value: either VAR, CONST, or LET depending on context. -For 'var' or 'const', the keyword should be the token last scanned. +For `var' or `const', the keyword should be the token last scanned. POS is the position where the node should start. It's sometimes the var/const/let keyword, and other times the beginning of the first token @@ -9448,16 +9611,10 @@ If NODE is non-nil, it is the AST node associated with the symbol." (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 +9624,7 @@ If NODE is non-nil, it is the AST node associated with the symbol." (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 +9632,7 @@ If NODE is non-nil, it is the AST node associated with the symbol." (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 +9700,20 @@ If NODE is non-nil, it is the AST node associated with the symbol." (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 +9747,12 @@ If NODE is non-nil, it is the AST node associated with the symbol." ((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 +9782,7 @@ If NODE is non-nil, it is the AST node associated with the symbol." (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 +9793,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 +9982,7 @@ to parse the operand (for prefix operators)." ((= 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 +10135,9 @@ Returns an expression tree that includes PN, the parent node." (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) @@ -10106,7 +10273,7 @@ This includes expressions of the forms: @* @*::attr @*::* @[expr] @*::[expr] @ns::[expr] -Called if we peeked an '@' token." +Called if we peeked an `@' token." (let ((tt (js2-get-prop-name-token)) (at-pos (js2-current-token-beg))) (cond @@ -10127,11 +10294,11 @@ Called if we peeked an '@' token." (defun js2-parse-property-name (at-pos s member-type-flags) "Check if :: follows name in which case it becomes qualified name. -AT-POS is a natural number if we just read an '@' token, else nil. -S is the name or string that was matched: an identifier, 'throw' or '*'. -MEMBER-TYPE-FLAGS is a bit set tracking whether we're a '.' or '..' child. +AT-POS is a natural number if we just read an `@' token, else nil. +S is the name or string that was matched: an identifier, `throw' or `*'. +MEMBER-TYPE-FLAGS is a bit set tracking whether we're a `.' or `..' child. -Returns a `js2-xml-ref-node' if it's an attribute access, a child of a '..' +Returns a `js2-xml-ref-node' if it's an attribute access, a child of a `..' operator, or the name is followed by ::. For a plain name, returns a `js2-name-node'. Returns a `js2-error-node' for malformed XML expressions." (let ((pos (or at-pos (js2-current-token-beg))) @@ -10205,6 +10372,8 @@ array-literals, array comprehensions and regular expressions." (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 +10390,8 @@ array-literals, array comprehensions and regular expressions." ((= 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 +10490,13 @@ array-literals, array comprehensions and regular expressions." (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 +10510,18 @@ array-literals, array comprehensions and regular expressions." :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 +10535,12 @@ array-literals, array comprehensions and regular expressions." (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 @@ -10389,7 +10553,7 @@ array-literals, array comprehensions and regular expressions." "Parse a legacy array comprehension (JavaScript 1.7). EXPR is the first expression after the opening left-bracket. POS is the beginning of the LB token preceding EXPR. -We should have just parsed the 'for' keyword before calling this function." +We should have just parsed the `for' keyword before calling this function." (let ((current-scope js2-current-scope) loops first filter result) (unwind-protect @@ -10426,7 +10590,7 @@ We should have just parsed the 'for' keyword before calling this function." (defun js2-parse-array-comp (pos) "Parse an ES6 array comprehension. POS is the beginning of the LB token. -We should have just parsed the 'for' keyword before calling this function." +We should have just parsed the `for' keyword before calling this function." (let ((pn (js2-parse-comprehension pos 'ARRAY))) (js2-must-match js2-RB "msg.no.bracket.arg" pos) pn)) @@ -10467,9 +10631,9 @@ We should have just parsed the 'for' keyword before calling this function." result)) (defun js2-parse-comp-loop (pn &optional only-of-p) - "Parse a 'for [each] (foo [in|of] bar)' expression in an Array comprehension. + "Parse a `for [each] (foo [in|of] bar)' expression in an Array comprehension. The current token should be the initial FOR. -If ONLY-OF-P is non-nil, only the 'for (foo of bar)' form is allowed." +If ONLY-OF-P is non-nil, only the `for (foo of bar)' form is allowed." (let ((pos (js2-comp-loop-node-pos pn)) tt iter obj foreach-p forof-p in-pos each-pos lp rp) (when (and (not only-of-p) (js2-match-token js2-NAME)) @@ -10526,6 +10690,7 @@ If ONLY-OF-P is non-nil, only the 'for (foo of bar)' form is allowed." (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 +10735,22 @@ If ONLY-OF-P is non-nil, only the 'for (foo of bar)' form is allowed." (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 +10775,26 @@ expression)." (= 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 +10806,10 @@ expression)." (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 +10833,16 @@ expression)." (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 +10853,34 @@ expression)." (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 +10895,38 @@ When `js2-is-in-destructuring' is t, forms like {a, b, c} will be permitted." (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 +10968,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 +10982,24 @@ and expression closure style is also supported 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 +11020,68 @@ And, if CHECK-ACTIVATION-P is non-nil, use the value of TOKEN." (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 @@ -11310,7 +11577,23 @@ Selecting an error will jump it to the corresponding source-buffer error. (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 +11930,7 @@ PARSE-STATUS is as documented in `parse-partial-sexp'." (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 +12577,10 @@ it marks the next defun after the ones already marked." (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) @@ -12355,7 +12638,7 @@ it marks the next defun after the ones already marked." (defun js2-search-object-for-prop (object prop-names) "Return node in OBJECT that matches PROP-NAMES or nil. PROP-NAMES is a list of values representing a path to a value in OBJECT. -i.e. ('name' 'value') = {name : { value: 3}}" +i.e. (\\='name\\=' \\='value\\=') = {name : { value: 3}}" (let (node (temp-object object) (temp t) ;temporay node