[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] master 2763c9a 33/33: Merge commit 'f3a899bb1c36e25e078eed2890eb2
From: |
Dmitry Gutov |
Subject: |
[elpa] master 2763c9a 33/33: Merge commit 'f3a899bb1c36e25e078eed2890eb2fecb22f4175' from js2-mode |
Date: |
Sun, 12 Jul 2015 22:35:47 +0000 |
branch: master
commit 2763c9abdc4a8d5769ff25126534d94d316d0278
Merge: af721ab f3a899b
Author: Dmitry Gutov <address@hidden>
Commit: Dmitry Gutov <address@hidden>
Merge commit 'f3a899bb1c36e25e078eed2890eb2fecb22f4175' from js2-mode
---
packages/js2-mode/NEWS.md | 6 +
packages/js2-mode/js2-mode.el | 685 ++++++++++++++++++++++++++++---------
packages/js2-mode/tests/parser.el | 247 +++++++++++++-
3 files changed, 766 insertions(+), 172 deletions(-)
diff --git a/packages/js2-mode/NEWS.md b/packages/js2-mode/NEWS.md
index eea51d3..300bc84 100644
--- a/packages/js2-mode/NEWS.md
+++ b/packages/js2-mode/NEWS.md
@@ -1,5 +1,11 @@
# History of user-visible changes
+## 20150713
+
+* More comprehensive strict mode warnings and syntax errors.
+* New minor mode: `js2-highlight-unused-variables-mode`.
+* `js2-pretty-multiline-declarations` can take the value `dynamic` now.
+
## 20150202
Support for:
diff --git a/packages/js2-mode/js2-mode.el b/packages/js2-mode/js2-mode.el
index 4fc2823..4a6e733 100644
--- a/packages/js2-mode/js2-mode.el
+++ b/packages/js2-mode/js2-mode.el
@@ -7,7 +7,7 @@
;; Dmitry Gutov <address@hidden>
;; URL: https://github.com/mooz/js2-mode/
;; http://code.google.com/p/js2-mode/
-;; Version: 20150202
+;; Version: 20150713
;; Keywords: languages, javascript
;; Package-Requires: ((emacs "24.1") (cl-lib "0.5"))
@@ -188,7 +188,8 @@ Set `js2-include-rhino-externs' to t to include them.")
(defvar js2-node-externs
(mapcar 'symbol-name
'(__dirname __filename Buffer clearInterval clearTimeout require
- console exports global module process setInterval setTimeout))
+ console exports global module process setInterval setTimeout
+ querystring))
"Node.js externs.
Set `js2-include-node-externs' to t to include them.")
@@ -735,6 +736,7 @@ List of chars built up while scanning various tokens.")
(end -1)
(string "")
number
+ number-base
regexp-flags
comment-type
follows-eol-p)
@@ -832,6 +834,9 @@ Will only be used when we finish implementing the
interpreter.")
(js2-deflocal js2-is-in-destructuring nil
"True while parsing destructuring expression.")
+(js2-deflocal js2-in-use-strict-directive nil
+ "True while inside a script or function under strict mode.")
+
(defcustom js2-global-externs nil
"A list of any extern names you'd like to consider always declared.
This list is global and is used by all `js2-mode' files.
@@ -1155,6 +1160,11 @@ another file, or you've got a potential bug."
:type 'boolean
:group 'js2-mode)
+(defcustom js2-warn-about-unused-function-arguments nil
+ "Non-nil to treat function arguments like declared-but-unused variables."
+ :type 'booleanp
+ :group 'js2-mode)
+
(defcustom js2-include-jslint-globals t
"Non-nil to include the identifiers from JSLint global
declaration (see http://www.jslint.com/lint.html#global) in the
@@ -1662,6 +1672,9 @@ the correct number of ARGS must be provided."
(js2-msg "msg.no.paren.after.with"
"missing ) after with-statement object")
+(js2-msg "msg.no.with.strict"
+ "with statements not allowed in strict mode")
+
(js2-msg "msg.no.paren.after.let"
"missing ( after let")
@@ -1777,6 +1790,18 @@ the correct number of ARGS must be provided."
(js2-msg "msg.destruct.assign.no.init"
"Missing = in destructuring declaration")
+(js2-msg "msg.no.octal.strict"
+ "Octal numbers prohibited in strict mode.")
+
+(js2-msg "msg.dup.obj.lit.prop.strict"
+ "Property '%s' already defined in this object literal.")
+
+(js2-msg "msg.dup.param.strict"
+ "Parameter '%s' already declared in this function.")
+
+(js2-msg "msg.bad.id.strict"
+ "'%s' is not a valid identifier for this use in strict mode.")
+
;; ScriptRuntime
(js2-msg "msg.no.properties"
"%s has no properties.")
@@ -1793,6 +1818,12 @@ the correct number of ARGS must be provided."
(js2-msg "msg.undeclared.variable" ; added by js2-mode
"Undeclared variable or function '%s'")
+(js2-msg "msg.unused.variable" ; added by js2-mode
+ "Unused variable or function '%s'")
+
+(js2-msg "msg.uninitialized.variable" ; added by js2-mode
+ "Variable '%s' referenced but never initialized")
+
(js2-msg "msg.ref.undefined.prop"
"Reference to undefined property '%s'")
@@ -2173,17 +2204,6 @@ Returns nil if element is not found in the list."
(defsubst js2-flag-not-set-p (flags flag)
(zerop (logand flags flag)))
-(defmacro js2-with-underscore-as-word-syntax (&rest body)
- "Evaluate BODY with the _ character set to be word-syntax."
- (declare (indent 0) (debug t))
- (let ((old-syntax (make-symbol "old-syntax")))
- `(let ((,old-syntax (string (char-syntax ?_))))
- (unwind-protect
- (progn
- (modify-syntax-entry ?_ "w" js2-mode-syntax-table)
- ,@body)
- (modify-syntax-entry ?_ ,old-syntax js2-mode-syntax-table)))))
-
;;; AST struct and function definitions
;; flags for ast node property 'member-type (used for e4x operators)
@@ -3742,10 +3762,13 @@ Returns 0 if NODE is nil or its identifier field is
nil."
(js2-current-token-beg)))
(value
(js2-current-token-string))
(num-value
(js2-token-number
-
(js2-current-token))))))
+
(js2-current-token)))
+ (num-base
(js2-token-number-base
+
(js2-current-token))))))
"AST node for a number literal."
value ; the original string, e.g. "6.02e23"
- num-value) ; the parsed number value
+ num-value ; the parsed number value
+ num-base) ; the number's base
(put 'cl-struct-js2-number-node 'js2-visitor 'js2-visit-none)
(put 'cl-struct-js2-number-node 'js2-printer 'js2-print-number-node)
@@ -3942,16 +3965,18 @@ optional `js2-expr-node'"
len left
right op-pos)))
"AST node for an object literal prop:value entry.
-The `left' field is the property: a name node, string node or number node.
-The `right' field is a `js2-node' representing the initializer value.
-If the property is abbreviated, the node's `SHORTHAND' property is non-nil
-and both fields have the same value.")
+The `left' field is the property: a name node, string node,
+number node or expression node. The `right' field is a
+`js2-node' representing the initializer value. If the property
+is abbreviated, the node's `SHORTHAND' property is non-nil and
+both fields have the same value.")
(put 'cl-struct-js2-object-prop-node 'js2-visitor 'js2-visit-infix-node)
(put 'cl-struct-js2-object-prop-node 'js2-printer 'js2-print-object-prop-node)
(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)))))
@@ -3964,7 +3989,7 @@ and both fields have the same value.")
(if (not (js2-node-get-prop n 'SHORTHAND))
(progn
(insert ": ")
- (js2-print-ast (js2-object-prop-node-right n) 0)))))
+ (js2-print-ast right 0)))))
(cl-defstruct (js2-getter-setter-node
(:include js2-infix-node)
@@ -3981,13 +4006,23 @@ property `GETTER_SETTER' set to js2-GET, js2-SET, or
js2-FUNCTION. ")
(put 'cl-struct-js2-getter-setter-node 'js2-printer 'js2-print-getter-setter)
(defun js2-print-getter-setter (n i)
- (let ((pad (js2-make-pad i))
- (left (js2-getter-setter-node-left n))
- (right (js2-getter-setter-node-right n)))
+ (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)))))
(insert pad)
(if (/= (js2-node-type n) js2-FUNCTION)
(insert (if (= (js2-node-type n) js2-GET) "get " "set ")))
+ (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
@@ -6078,8 +6113,8 @@ its relevant fields and puts it into `js2-ti-tokens'."
while (js2-digit-p c))))
(js2-unget-char)
(let ((str (js2-set-string-from-buffer token)))
- (setf (js2-token-number token)
- (js2-string-to-number str base)))
+ (setf (js2-token-number token) (js2-string-to-number str base)
+ (js2-token-number-base token) base))
(throw 'return js2-NUMBER))
;; is it a string?
(when (or (memq c '(?\" ?\'))
@@ -6868,7 +6903,7 @@ of a simple name. Called before EXPR has a parent node."
"\\)"
"\\s-*\\({[^}]+}\\)?" ; optional type
"\\s-*\\[?\\([[:alnum:]_$\.]+\\)?\\]?" ; name
- "\\>")
+ "\\_>")
"Matches jsdoc tags with optional type and optional param name.")
(defconst js2-jsdoc-typed-tag-regexp
@@ -7068,8 +7103,161 @@ it is considered declared."
(member name js2-additional-externs)
(js2-get-defining-scope scope name pos))
(js2-report-warning "msg.undeclared.variable" name pos (- end pos)
- 'js2-external-variable))))
- (setq js2-recorded-identifiers nil)))
+ 'js2-external-variable))))))
+
+(defun js2--add-or-update-symbol (symbol inition used vars)
+ "Add or update SYMBOL entry in VARS, an hash table.
+SYMBOL is a js2-name-node, INITION either nil, t, or ?P,
+respectively meaning that SYMBOL is a mere declaration, an
+assignment or a function parameter; when USED is t, the symbol
+node is assumed to be an usage and thus added to the list stored
+in the cdr of the entry.
+"
+ (let* ((nm (js2-name-node-name symbol))
+ (es (js2-node-get-enclosing-scope symbol))
+ (ds (js2-get-defining-scope es nm)))
+ (when (and ds (not (equal nm "arguments")))
+ (let* ((sym (js2-scope-get-symbol ds nm))
+ (var (gethash sym vars))
+ (err-var-p (js2-catch-node-p ds)))
+ (unless inition
+ (setq inition err-var-p))
+ (if var
+ (progn
+ (when (and inition (not (equal (car var) ?P)))
+ (setcar var inition))
+ (when used
+ (push symbol (cdr var))))
+ ;; do not consider the declaration of catch parameter as an usage
+ (when (and err-var-p used)
+ (setq used nil))
+ (puthash sym (cons inition (if used (list symbol))) vars))))))
+
+(defun js2--classify-variables ()
+ "Collect and classify variables declared or used within js2-mode-ast.
+Traverse the whole ast tree returning a summary of the variables
+usage as an hash-table, keyed by their corresponding symbol table
+entry.
+Each variable is described by a tuple where the car is a flag
+indicating whether the variable has been initialized and the cdr
+is a possibly empty list of name nodes where it is used. External
+symbols, i.e. those not present in the whole scopes hierarchy,
+are ignored."
+ (let ((vars (make-hash-table :test #'eq :size 100)))
+ (js2-visit-ast
+ js2-mode-ast
+ (lambda (node end-p)
+ (when (null end-p)
+ (cond
+ ((js2-var-init-node-p node)
+ ;; take note about possibly initialized declarations
+ (let ((target (js2-var-init-node-target node))
+ (initializer (js2-var-init-node-initializer node)))
+ (when target
+ (let* ((parent (js2-node-parent node))
+ (grandparent (if parent (js2-node-parent parent)))
+ (inited (not (null initializer))))
+ (unless inited
+ (setq inited
+ (and grandparent
+ (js2-for-in-node-p grandparent)
+ (memq target
+ (mapcar #'js2-var-init-node-target
+ (js2-var-decl-node-kids
+ (js2-for-in-node-iterator
grandparent)))))))
+ (js2--add-or-update-symbol target inited nil vars)))))
+
+ ((js2-assign-node-p node)
+ ;; take note about assignments
+ (let ((left (js2-assign-node-left node)))
+ (when (js2-name-node-p left)
+ (js2--add-or-update-symbol left t nil vars))))
+
+ ((js2-prop-get-node-p node)
+ ;; handle x.y.z nodes, considering only x
+ (let ((left (js2-prop-get-node-left node)))
+ (when (js2-name-node-p left)
+ (js2--add-or-update-symbol left nil t vars))))
+
+ ((js2-name-node-p node)
+ ;; take note about used variables
+ (let ((parent (js2-node-parent node)))
+ (when parent
+ (unless (or (and (js2-var-init-node-p parent) ; handled above
+ (eq node (js2-var-init-node-target parent)))
+ (and (js2-assign-node-p parent)
+ (eq node (js2-assign-node-left parent)))
+ (js2-prop-get-node-p parent))
+ (let ((used t) inited)
+ (cond
+ ((and (js2-function-node-p parent)
+ (js2-wrapper-function-p parent))
+ (setq inited (if (memq node (js2-function-node-params
parent)) ?P t)))
+
+ ((js2-for-in-node-p parent)
+ (if (eq node (js2-for-in-node-iterator parent))
+ (setq inited t used nil)))
+
+ ((js2-function-node-p parent)
+ (setq inited (if (memq node (js2-function-node-params
parent)) ?P t)
+ used nil)))
+
+ (unless used
+ (let ((grandparent (js2-node-parent parent)))
+ (when grandparent
+ (setq used (js2-return-node-p grandparent)))))
+
+ (js2--add-or-update-symbol node inited used vars))))))))
+ t))
+ vars))
+
+(defun js2--get-name-node (node)
+ (cond
+ ((js2-name-node-p node) node)
+ ((js2-function-node-p node)
+ (js2-function-node-name node))
+ ((js2-class-node-p node)
+ (js2-class-node-name node))
+ ((js2-comp-loop-node-p node)
+ (js2-comp-loop-node-iterator node))
+ (t node)))
+
+(defun js2--highlight-unused-variable (symbol info)
+ (let ((name (js2-symbol-name symbol))
+ (inited (car info))
+ (refs (cdr info))
+ pos len)
+ (unless (and inited refs)
+ (if refs
+ (dolist (ref refs)
+ (setq pos (js2-node-abs-pos ref))
+ (setq len (js2-name-node-len ref))
+ (js2-report-warning "msg.uninitialized.variable" name pos len
+ 'js2-warning))
+ (when (or js2-warn-about-unused-function-arguments
+ (not (eq inited ?P)))
+ (let* ((symn (js2-symbol-ast-node symbol))
+ (namen (js2--get-name-node symn)))
+ (unless (js2-node-top-level-decl-p namen)
+ (setq pos (js2-node-abs-pos namen))
+ (setq len (js2-name-node-len namen))
+ (js2-report-warning "msg.unused.variable" name pos len
+ 'js2-warning))))))))
+
+(defun js2-highlight-unused-variables ()
+ "Highlight unused variables."
+ (let ((vars (js2--classify-variables)))
+ (maphash #'js2--highlight-unused-variable vars)))
+
+;;;###autoload
+(define-minor-mode js2-highlight-unused-variables-mode
+ "Toggle highlight of unused variables."
+ :lighter ""
+ (if js2-highlight-unused-variables-mode
+ (add-hook 'js2-post-parse-callbacks
+ #'js2-highlight-unused-variables nil t)
+ (remove-hook 'js2-post-parse-callbacks
+ #'js2-highlight-unused-variables t)))
(defun js2-set-default-externs ()
"Set the value of `js2-default-externs' based on the various
@@ -7686,6 +7874,15 @@ Returns t on match, nil if no match."
(defsubst js2-exit-switch ()
(pop js2-loop-and-switch-set))
+(defsubst js2-get-directive (node)
+ "Return NODE's value if it is a directive, nil otherwise.
+
+A directive is an otherwise-meaningless expression statement
+consisting of a string literal, such as \"use strict\"."
+ (and (js2-expr-stmt-node-p node)
+ (js2-string-node-p (setq node (js2-expr-stmt-node-expr node)))
+ (js2-string-node-value node)))
+
(defun js2-parse (&optional buf cb)
"Tell the js2 parser to parse a region of JavaScript.
@@ -7747,14 +7944,18 @@ leaving a statement, an expression, or a function
definition."
Scanner should be initialized."
(let ((pos js2-ts-cursor)
(end js2-ts-cursor) ; in case file is empty
- root n tt)
+ root n tt
+ (in-directive-prologue t)
+ (js2-in-use-strict-directive js2-in-use-strict-directive)
+ directive)
;; initialize buffer-local parsing vars
(setf root (make-js2-ast-root :buffer (buffer-name) :pos pos)
js2-current-script-or-fn root
js2-current-scope root
js2-nesting-of-function 0
js2-labeled-stmt nil
- js2-recorded-identifiers nil) ; for js2-highlight
+ js2-recorded-identifiers nil ; for js2-highlight
+ js2-in-use-strict-directive nil)
(while (/= (setq tt (js2-get-token)) js2-EOF)
(if (= tt js2-FUNCTION)
(progn
@@ -7763,7 +7964,14 @@ Scanner should be initialized."
(js2-parse-function-stmt))))
;; not a function - parse a statement
(js2-unget-token)
- (setq n (js2-parse-statement)))
+ (setq n (js2-parse-statement))
+ (when in-directive-prologue
+ (setq directive (js2-get-directive n))
+ (cond
+ ((null directive)
+ (setq in-directive-prologue nil))
+ ((string= directive "use strict")
+ (setq js2-in-use-strict-directive t)))))
;; add function or statement to script
(setq end (js2-node-end n))
(js2-block-node-push root n))
@@ -7801,16 +8009,34 @@ Scanner should be initialized."
(let ((pos (js2-current-token-beg)) ; LC position
(pn (make-js2-block-node)) ; starts at LC position
tt
- end)
+ end
+ not-in-directive-prologue
+ node
+ directive)
(cl-incf js2-nesting-of-function)
(unwind-protect
(while (not (or (= (setq tt (js2-peek-token)) js2-ERROR)
(= tt js2-EOF)
(= tt js2-RC)))
- (js2-block-node-push pn (if (/= tt js2-FUNCTION)
- (js2-parse-statement)
- (js2-get-token)
- (js2-parse-function-stmt))))
+ (js2-block-node-push
+ pn
+ (if (/= tt js2-FUNCTION)
+ (if not-in-directive-prologue
+ (js2-parse-statement)
+ (setq node (js2-parse-statement)
+ directive (js2-get-directive node))
+ (cond
+ ((null directive)
+ (setq not-in-directive-prologue t))
+ ((string= directive "use strict")
+ ;; Back up and reparse the function, because new rules apply
+ ;; to the function name and parameters.
+ (when (not js2-in-use-strict-directive)
+ (setq js2-in-use-strict-directive t)
+ (throw 'reparse t))))
+ node)
+ (js2-get-token)
+ (js2-parse-function-stmt))))
(cl-decf js2-nesting-of-function))
(setq end (js2-current-token-end)) ; assume no curly and leave at current
token
(if (js2-must-match js2-RC "msg.no.brace.after.body" pos)
@@ -7821,9 +8047,10 @@ Scanner should be initialized."
(js2-node-add-children fn-node pn)
pn))
-(defun js2-define-destruct-symbols (node decl-type face &optional
ignore-not-in-block)
- "Declare and fontify destructuring parameters inside NODE.
-NODE is either `js2-array-node', `js2-object-node', or `js2-name-node'."
+(defun js2-define-destruct-symbols-internal
+ (node decl-type face &optional ignore-not-in-block name-nodes)
+ "Internal version of `js2-define-destruct-symbols'. The only
+difference is that NAME-NODES is passed down recursively."
(cond
((js2-name-node-p node)
(let (leftpos)
@@ -7832,26 +8059,70 @@ NODE is either `js2-array-node', `js2-object-node', or
`js2-name-node'."
(when face
(js2-set-face (setq leftpos (js2-node-abs-pos node))
(+ leftpos (js2-node-len node))
- face 'record))))
+ face 'record))
+ (setq name-nodes (append name-nodes (list node)))))
((js2-object-node-p node)
(dolist (elem (js2-object-node-elems node))
- (js2-define-destruct-symbols
- ;; In abbreviated destructuring {a, b}, right == left.
- (js2-object-prop-node-right elem)
- decl-type face ignore-not-in-block)))
+ (setq name-nodes
+ (append name-nodes
+ (js2-define-destruct-symbols-internal
+ ;; In abbreviated destructuring {a, b}, right == left.
+ (js2-object-prop-node-right elem)
+ decl-type face ignore-not-in-block name-nodes)))))
((js2-array-node-p node)
(dolist (elem (js2-array-node-elems node))
(when elem
- (js2-define-destruct-symbols elem decl-type face
ignore-not-in-block))))
+ (setq name-nodes
+ (append name-nodes
+ (js2-define-destruct-symbols-internal
+ elem decl-type face ignore-not-in-block name-nodes))))))
(t (js2-report-error "msg.no.parm" nil (js2-node-abs-pos node)
- (js2-node-len node)))))
+ (js2-node-len node))))
+ name-nodes)
+
+(defun js2-define-destruct-symbols (node decl-type face &optional
ignore-not-in-block)
+ "Declare and fontify destructuring parameters inside NODE.
+NODE is either `js2-array-node', `js2-object-node', or `js2-name-node'.
+
+Return a list of `js2-name-node' nodes representing the symbols
+declared; probably to check them for errors."
+ (js2-define-destruct-symbols-internal node decl-type face
ignore-not-in-block))
+
+(defvar js2-illegal-strict-identifiers
+ '("eval" "arguments")
+ "Identifiers not allowed as variables in strict mode.")
+
+(defun js2-check-strict-identifier (name-node)
+ "Check that NAME-NODE makes a legal strict mode identifier."
+ (when js2-in-use-strict-directive
+ (let ((param-name (js2-name-node-name name-node)))
+ (when (member param-name js2-illegal-strict-identifiers)
+ (js2-report-error "msg.bad.id.strict" param-name
+ (js2-node-abs-pos name-node) (js2-node-len
name-node))))))
+
+(defun js2-check-strict-function-params (preceding-params params)
+ "Given PRECEDING-PARAMS in a function's parameter list, check
+for strict mode errors caused by PARAMS."
+ (when js2-in-use-strict-directive
+ (dolist (param params)
+ (let ((param-name (js2-name-node-name param)))
+ (js2-check-strict-identifier param)
+ (when (cl-some (lambda (param)
+ (string= (js2-name-node-name param) param-name))
+ preceding-params)
+ (js2-report-error "msg.dup.param.strict" param-name
+ (js2-node-abs-pos param) (js2-node-len param)))))))
(defun js2-parse-function-params (function-type fn-node pos)
+ "Parse the parameters of a function of FUNCTION-TYPE
+represented by FN-NODE at POS."
(if (js2-match-token js2-RP)
(setf (js2-function-node-rp fn-node) (- (js2-current-token-beg) pos))
(let ((paren-free-arrow (and (eq function-type 'FUNCTION_ARROW)
(eq (js2-current-token-type) js2-NAME)))
- params param default-found rest-param-at)
+ params param
+ param-name-nodes new-param-name-nodes
+ default-found rest-param-at)
(when paren-free-arrow
(js2-unget-token))
(cl-loop for tt = (js2-peek-token)
@@ -7863,10 +8134,11 @@ NODE is either `js2-array-node', `js2-object-node', or
`js2-name-node'."
(js2-get-token)
(when default-found
(js2-report-error "msg.no.default.after.default.param"))
- (setq param (js2-parse-destruct-primary-expr))
- (js2-define-destruct-symbols param
- js2-LP
- 'js2-function-param)
+ (setq param (js2-parse-destruct-primary-expr)
+ 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))
;; variable name
(t
@@ -7880,6 +8152,8 @@ NODE is either `js2-array-node', `js2-object-node', or
`js2-name-node'."
(js2-record-face 'js2-function-param)
(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 (or (and default-found
(not rest-param-at)
@@ -7963,10 +8237,7 @@ Last token scanned is the close-curly for the function
body."
(js2-must-match js2-LP "msg.no.paren.parms")
(js2-parse-function 'FUNCTION_EXPRESSION pos star-p name)))
-(defun js2-parse-function (function-type pos star-p &optional 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."
+(defun js2-parse-function-internal (function-type pos star-p &optional name)
(let (fn-node lp)
(if (= (js2-current-token-type) js2-LP) ; eventually matched LP?
(setq lp (js2-current-token-beg)))
@@ -7981,7 +8252,9 @@ arrow function), NAME is js2-name-node."
(when (and (eq function-type 'FUNCTION_STATEMENT)
(cl-plusp (js2-name-node-length name)))
;; Function statements define a symbol in the enclosing scope
- (js2-define-symbol js2-FUNCTION (js2-name-node-name name) fn-node)))
+ (js2-define-symbol js2-FUNCTION (js2-name-node-name name) fn-node))
+ (when js2-in-use-strict-directive
+ (js2-check-strict-identifier name)))
(if (or (js2-inside-function) (cl-plusp js2-nesting-of-with))
;; 1. Nested functions are not affected by the dynamic scope flag
;; as dynamic scope is already a parent of their scope.
@@ -8027,6 +8300,29 @@ arrow function), NAME is js2-name-node."
(setf (js2-scope-parent-scope fn-node) js2-current-scope)
fn-node))
+(defun js2-parse-function (function-type pos star-p &optional 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."
+ (let ((continue t)
+ ts-state
+ fn-node
+ ;; Preserve strict state outside this function.
+ (js2-in-use-strict-directive js2-in-use-strict-directive))
+ ;; Parse multiple times if a new strict mode directive is discovered in the
+ ;; function body, as new rules will be retroactively applied to the
legality
+ ;; of function names and parameters.
+ (while continue
+ (setq ts-state (make-js2-ts-state))
+ (setq continue (catch 'reparse
+ (setq fn-node (js2-parse-function-internal
+ function-type pos star-p name))
+ ;; Don't continue.
+ nil))
+ (when continue
+ (js2-ts-seek ts-state)))
+ fn-node))
+
(defun js2-parse-statements (&optional parent)
"Parse a statement list. Last token consumed must be js2-LC.
@@ -8714,7 +9010,8 @@ Last matched token must be js2-FOR."
(t
(js2-must-match-name "msg.bad.catchcond")
(setq param (js2-create-name-node))
- (js2-define-symbol js2-LET (js2-current-token-string) param))))
+ (js2-define-symbol js2-LET (js2-current-token-string) param)
+ (js2-check-strict-identifier param))))
;; Catch condition.
(if (js2-match-token js2-IF)
(setq guard-kwd (- (js2-current-token-beg) catch-pos)
@@ -8844,6 +9141,8 @@ does not match an existing label, reports an error and
returns nil."
(defun js2-parse-with ()
"Parser for with-statement. Last matched token must be js2-WITH."
+ (when js2-in-use-strict-directive
+ (js2-report-error "msg.no.with.strict"))
(let ((pos (js2-current-token-beg))
obj body pn lp rp)
(if (js2-must-match js2-LP "msg.no.paren.with")
@@ -9160,7 +9459,8 @@ Returns the parsed `js2-var-decl-node' expression node."
nbeg (js2-current-token-beg)
nend (js2-current-token-end)
end nend)
- (js2-define-symbol decl-type (js2-current-token-string) name
js2-in-for-init)))
+ (js2-define-symbol decl-type (js2-current-token-string) name
js2-in-for-init)
+ (js2-check-strict-identifier name)))
(when (js2-match-token js2-ASSIGN)
(setq init (js2-parse-assign-expr)
end (js2-node-end init))
@@ -9241,8 +9541,12 @@ If NODE is non-nil, it is the AST node associated with
the symbol."
(len (if node (js2-node-len node))))
(cond
((and symbol ; already defined
- (or (= sdt js2-CONST) ; old version is const
- (= decl-type js2-CONST) ; new version is const
+ (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))))
@@ -9254,15 +9558,21 @@ If NODE is non-nil, it is the AST node associated with
the symbol."
((= sdt js2-FUNCTION) "msg.function.redecl")
(t "msg.parm.redecl"))
name pos len))
- ((= decl-type js2-LET)
- (if (and (not ignore-not-in-block)
+ ((or (= decl-type js2-LET)
+ ;; strict mode const is scoped to the current LexicalEnvironment
+ (and js2-in-use-strict-directive
+ (= decl-type js2-CONST)))
+ (if (and (= decl-type js2-LET)
+ (not ignore-not-in-block)
(or (= (js2-node-type js2-current-scope) js2-IF)
(js2-loop-node-p js2-current-scope)))
(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-CONST)
- (= decl-type js2-FUNCTION))
+ (= decl-type js2-FUNCTION)
+ ;; sloppy mode const is scoped to the current VariableEnvironment
+ (and (not js2-in-use-strict-directive)
+ (= decl-type js2-CONST)))
(if symbol
(if (and js2-strict-var-redeclaration-warning (= sdt js2-VAR))
(js2-add-strict-warning "msg.var.redecl" name)
@@ -9347,6 +9657,10 @@ If NODE is non-nil, it is the AST node associated with
the symbol."
;; tt express assignment (=, |=, ^=, ..., %=)
(setq op-pos (- (js2-current-token-beg) pos) ; relative
left pn)
+ ;; The assigned node could be a js2-prop-get-node (foo.bar = 0), we
only
+ ;; care about assignment to strict variable names.
+ (when (js2-name-node-p left)
+ (js2-check-strict-identifier left))
(setq right (js2-parse-assign-expr)
pn (make-js2-assign-node :type tt
:pos pos
@@ -9976,7 +10290,7 @@ For instance, @[expr], @*::[expr], or ns::[expr]."
"Parse a literal (leaf) expression of some sort.
Includes complex literals such as functions, object-literals,
array-literals, array comprehensions and regular expressions."
- (let (tt)
+ (let (tt node)
(setq tt (js2-current-token-type))
(cond
((= tt js2-CLASS)
@@ -9997,7 +10311,11 @@ array-literals, array comprehensions and regular
expressions."
((= tt js2-NAME)
(js2-parse-name tt))
((= tt js2-NUMBER)
- (make-js2-number-node))
+ (setq node (make-js2-number-node))
+ (when (and js2-in-use-strict-directive
+ (= (js2-number-node-num-base node) 8))
+ (js2-report-error "msg.no.octal.strict"))
+ node)
((or (= tt js2-STRING) (= tt js2-NO_SUBS_TEMPLATE))
(make-js2-string-node :type tt))
((= tt js2-TEMPLATE_HEAD)
@@ -10214,7 +10532,7 @@ We should have just parsed the 'for' keyword before
calling this function."
pn))
(defun js2-parse-comprehension (pos form)
- (let (loops filters expr result)
+ (let (loops filters expr result last)
(unwind-protect
(progn
(js2-unget-token)
@@ -10225,7 +10543,8 @@ We should have just parsed the 'for' keyword before
calling this function."
(js2-parse-comp-loop loop)))
(while (js2-match-token js2-IF)
(push (car (js2-parse-condition)) filters))
- (setq expr (js2-parse-assign-expr)))
+ (setq expr (js2-parse-assign-expr))
+ (setq last (car loops)))
(dolist (_ loops)
(js2-pop-scope)))
(setq result (make-js2-comp-node :pos pos
@@ -10236,6 +10555,7 @@ We should have just parsed the 'for' keyword before
calling this function."
:form form))
(apply #'js2-node-add-children result (js2-comp-node-loops result))
(apply #'js2-node-add-children result expr (js2-comp-node-filters result))
+ (setf (js2-scope-parent-scope result) last)
result))
(defun js2-parse-comp-loop (pn &optional only-of-p)
@@ -10340,42 +10660,61 @@ If ONLY-OF-P is non-nil, only the 'for (foo of bar)'
form is allowed."
(apply #'js2-node-add-children result (js2-object-node-elems result))
result))
+(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
+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)))))
+
(defun js2-parse-object-literal-elems (&optional class-p)
(let ((pos (js2-current-token-beg))
(static nil)
(continue t)
- tt elems elem after-comma)
+ tt elems elem
+ elem-key-string previous-elem-key-string
+ after-comma previous-token)
(while continue
(setq tt (js2-get-prop-name-token)
static nil
- elem nil)
+ elem nil
+ previous-token nil)
+ ;; Handle 'static' keyword only if we're in a class
(when (and class-p (= js2-NAME tt)
(string= "static" (js2-current-token-string)))
(js2-record-face 'font-lock-keyword-face)
(setq static t
tt (js2-get-prop-name-token)))
+ ;; Handle generator * before the property name for in-line functions
+ (when (and (>= js2-language-version 200)
+ (= js2-MUL tt))
+ (setq previous-token (js2-current-token)
+ tt (js2-get-prop-name-token)))
+ ;; Handle 'get' or 'set' keywords
+ (let ((prop (js2-current-token-string)))
+ (when (and (>= js2-language-version 200)
+ (= js2-NAME tt)
+ (or (string= prop "get")
+ (string= prop "set"))
+ (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
- ;; {foo: ...}, {'foo': ...}, {foo, bar, ...},
- ;; {get foo() {...}}, {set foo(x) {...}}, or {foo(x) {...}}
- ;; TODO(sdh): support *foo() {...}
- ((or (= js2-NAME tt)
- (= tt js2-STRING))
+ ;; Found a 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))
+ elem (js2-parse-named-prop tt pos previous-token))
(if (and (null elem)
(not js2-recover-from-parse-errors))
(setq continue nil)))
- ;; {[Symbol.iterator]: ...}
- ((and (= tt js2-LB)
- (>= js2-language-version 200))
- (let ((expr (js2-parse-expr)))
- (js2-must-match js2-RB "msg.missing.computed.rb")
- (setq after-comma nil
- elem (js2-parse-plain-property expr))))
- ;; {12: x} or {10.7: x}
- ((= tt js2-NUMBER)
- (setq after-comma nil
- elem (js2-parse-plain-property (make-js2-number-node))))
;; Break out of loop, and handle trailing commas.
((or (= tt js2-RC)
(= tt js2-EOF))
@@ -10400,56 +10739,84 @@ If ONLY-OF-P is non-nil, only the 'for (foo of bar)'
form is allowed."
(setq after-comma (js2-current-token-end)))
(js2-unget-token)
(unless class-p (setq continue nil))))
- ;; Append any parsed element.
- (if elem (push elem elems))) ; end loop
+ (when elem
+ (when (and js2-in-use-strict-directive
+ (setq elem-key-string (js2-property-key-string elem))
+ (cl-some
+ (lambda (previous-elem)
+ (and (setq previous-elem-key-string
+ (js2-property-key-string previous-elem))
+ (string= previous-elem-key-string elem-key-string)))
+ elems))
+ (js2-report-error "msg.dup.obj.lit.prop.strict"
+ elem-key-string
+ (js2-node-abs-pos (js2-infix-node-left elem))
+ (js2-node-len (js2-infix-node-left elem))))
+ ;; Append any parsed element.
+ (push elem elems))) ; end loop
(js2-must-match js2-RC "msg.no.brace.prop")
(nreverse elems)))
-(defun js2-parse-named-prop (tt)
+(defun js2-parse-named-prop (tt pos 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 ((string-prop (and (= tt js2-STRING)
- (make-js2-string-node)))
- expr
- (ppos (js2-current-token-beg))
- (pend (js2-current-token-end))
- (name (js2-create-name-node))
- (prop (js2-current-token-string)))
+ (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"))))
+ (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-set-face (js2-token-beg previous-token)
+ (js2-token-end previous-token)
+ 'font-lock-keyword-face 'record)) ; get/set
(cond
- ;; getter/setter prop
- ((and (= tt js2-NAME)
- (= (js2-peek-token) js2-NAME)
- (or (string= prop "get")
- (string= prop "set")))
- (js2-get-token)
- (js2-set-face ppos pend 'font-lock-keyword-face 'record) ; get/set
- (js2-record-face 'font-lock-function-name-face) ; for peeked name
- (setq name (js2-create-name-node)) ; discard get/set & use peeked name
- (js2-parse-getter-setter-prop ppos name prop))
;; method definition: {f() {...}}
((and (= (js2-peek-token) js2-LP)
(>= js2-language-version 200))
- (js2-record-face 'font-lock-function-name-face) ; name
- (js2-parse-getter-setter-prop ppos name ""))
+ (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))
;; regular prop
(t
- (prog1
- (setq expr (js2-parse-plain-property (or string-prop name)))
- (when (and (not string-prop)
+ (let ((beg (js2-current-token-beg))
+ (end (js2-current-token-end))
+ (expr (js2-parse-plain-property key)))
+ (when (and (= tt js2-NAME)
(not js2-is-in-destructuring)
js2-highlight-external-variables
(js2-node-get-prop expr 'SHORTHAND))
- (js2-record-name-node name))
- (js2-set-face ppos pend
+ (js2-record-name-node key))
+ (js2-set-face beg end
(if (js2-function-node-p
(js2-object-prop-node-right expr))
'font-lock-function-name-face
'font-lock-variable-name-face)
- 'record))))))
+ 'record)
+ expr)))))
(defun js2-parse-plain-property (prop)
"Parse a non-getter/setter property in an object literal.
-PROP is the node representing the property: a number, name or string."
+PROP is the node representing the property: a number, name,
+string or expression."
(let* ((tt (js2-get-token))
(pos (js2-node-pos prop))
colon expr result)
@@ -10499,7 +10866,7 @@ 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.
-GET-P is non-nil if the keyword was `get'."
+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)
@@ -10512,6 +10879,8 @@ GET-P is non-nil if the keyword was `get'."
(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))
(setq end (js2-node-end fn)
result (make-js2-getter-setter-node :type type
:pos pos
@@ -10664,7 +11033,7 @@ and comments have been removed."
(and (looking-at js2-indent-operator-re)
(or (not (looking-at ":"))
(save-excursion
- (and (js2-re-search-backward "[?:{]\\|\\<case\\>" nil t)
+ (and (js2-re-search-backward "[?:{]\\|\\_<case\\_>" nil t)
(looking-at "?"))))))
(defun js2-continued-expression-p ()
@@ -10693,20 +11062,20 @@ spanning several lines requires that the start of the
loop is
indented to the same column as the current line."
(interactive)
(save-excursion
- (when (looking-at "\\s-*\\<while\\>")
+ (when (looking-at "\\s-*\\_<while\\_>")
(if (save-excursion
(skip-chars-backward "[ \t\n]*}")
(looking-at "[ \t\n]*}"))
(save-excursion
- (backward-list) (backward-word 1) (looking-at "\\<do\\>"))
- (js2-re-search-backward "\\<do\\>" (point-at-bol) t)
- (or (looking-at "\\<do\\>")
+ (backward-list) (backward-word 1) (looking-at "\\_<do\\_>"))
+ (js2-re-search-backward "\\_<do\\_>" (point-at-bol) t)
+ (or (looking-at "\\_<do\\_>")
(let ((saved-indent (current-indentation)))
- (while (and (js2-re-search-backward "^[ \t]*\\<" nil t)
+ (while (and (js2-re-search-backward "^[ \t]*\\_<" nil t)
(/= (current-indentation) saved-indent)))
- (and (looking-at "[ \t]*\\<do\\>")
+ (and (looking-at "[ \t]*\\_<do\\_>")
(not (js2-re-search-forward
- "\\<while\\>" (point-at-eol) t))
+ "\\_<while\\_>" (point-at-eol) t))
(= (current-indentation) saved-indent))))))))
(defun js2-multiline-decl-indentation ()
@@ -10844,8 +11213,8 @@ indentation is aligned to that column."
(let* ((ctrl-stmt-indent (js2-ctrl-statement-indentation))
(at-closing-bracket (looking-at "[]})]"))
(same-indent-p (or at-closing-bracket
- (looking-at "\\<case\\>[^:]")
- (and (looking-at "\\<default:")
+ (looking-at "\\_<case\\_>[^:]")
+ (and (looking-at "\\_<default:")
(save-excursion
(js2-backward-sws)
(not (memq (char-before) '(?, ?{)))))))
@@ -10925,15 +11294,6 @@ indentation is aligned to that column."
(interactive)
(while (forward-comment 1)))
-(defun js2-current-indent (&optional pos)
- "Return column of indentation on current line.
-If POS is non-nil, go to that point and return indentation for that line."
- (save-excursion
- (if pos
- (goto-char pos))
- (back-to-indentation)
- (current-column)))
-
(defun js2-arglist-close ()
"Return non-nil if we're on a line beginning with a close-paren/brace."
(save-excursion
@@ -10988,7 +11348,7 @@ of the buffer to the current point. NORMAL-COL is the
indentation
column computed by the heuristic guesser based on current paren,
bracket, brace and statement nesting. If BACKWARDS, cycle positions
in reverse."
- (let ((cur-indent (js2-current-indent))
+ (let ((cur-indent (current-indentation))
(old-buffer-undo-list buffer-undo-list)
;; Emacs 21 only has `count-lines', not `line-number-at-pos'
(current-line (save-excursion
@@ -11171,22 +11531,21 @@ If so, we don't ever want to use bounce-indent."
(point))))
;; Don't touch multiline strings.
(unless (nth 3 parse-status)
- (js2-with-underscore-as-word-syntax
- (if (nth 4 parse-status)
- (js2-lineup-comment parse-status)
- (setq indent-col (js2-proper-indentation parse-status))
- ;; See comments below about `js2-mode-last-indented-line'.
- (cond
- ;; bounce-indenting is disabled during electric-key indent.
- ;; It doesn't work well on first line of buffer.
- ((and js2-bounce-indent-p
- (not (js2-same-line (point-min)))
- (not (js2-1-line-comment-continuation-p)))
- (js2-bounce-indent indent-col parse-status bounce-backwards))
- ;; just indent to the guesser's likely spot
- (t (indent-line-to indent-col))))
- (when (cl-plusp offset)
- (forward-char offset))))))
+ (if (nth 4 parse-status)
+ (js2-lineup-comment parse-status)
+ (setq indent-col (js2-proper-indentation parse-status))
+ ;; See comments below about `js2-mode-last-indented-line'.
+ (cond
+ ;; bounce-indenting is disabled during electric-key indent.
+ ;; It doesn't work well on first line of buffer.
+ ((and js2-bounce-indent-p
+ (not (js2-same-line (point-min)))
+ (not (js2-1-line-comment-continuation-p)))
+ (js2-bounce-indent indent-col parse-status bounce-backwards))
+ ;; just indent to the guesser's likely spot
+ (t (indent-line-to indent-col))))
+ (when (cl-plusp offset)
+ (forward-char offset)))))
(defun js2-indent-region (start end)
"Indent the region, but don't use bounce indenting."
@@ -12297,17 +12656,15 @@ destroying the region selection."
"Replacement for `find-tag-default'.
`find-tag-default' returns a ridiculous answer inside comments."
(let (beg end)
- (js2-with-underscore-as-word-syntax
- (save-excursion
- (if (and (not (looking-at "[[:alnum:]_$]"))
- (looking-back "[[:alnum:]_$]"))
- (setq beg (progn (forward-word -1) (point))
- end (progn (forward-word 1) (point)))
- (setq beg (progn (forward-word 1) (point))
- end (progn (forward-word -1) (point))))
- (replace-regexp-in-string
- "[\"']" ""
- (buffer-substring-no-properties beg end))))))
+ (save-excursion
+ (if (looking-at "\\_>")
+ (setq beg (progn (forward-symbol -1) (point))
+ end (progn (forward-symbol 1) (point)))
+ (setq beg (progn (forward-symbol 1) (point))
+ end (progn (forward-symbol -1) (point))))
+ (replace-regexp-in-string
+ "[\"']" ""
+ (buffer-substring-no-properties beg end)))))
(defun js2-mode-forward-sibling ()
"Move to the end of the sibling following point in parent.
diff --git a/packages/js2-mode/tests/parser.el
b/packages/js2-mode/tests/parser.el
index 0dd1502..bfc5653 100644
--- a/packages/js2-mode/tests/parser.el
+++ b/packages/js2-mode/tests/parser.el
@@ -198,6 +198,18 @@ the test."
(js2-deftest-parse object-literal-computed-keys
"var x = {[Symbol.iterator]: function() {}};")
+(js2-deftest-parse object-literal-computed-function-keys
+ "var x = {[foo + bar](y) { return y;\n}};")
+
+(js2-deftest-parse object-literal-computed-getter-key
+ "var x = {get [foo + bar]() { return 42;\n}};")
+
+(js2-deftest-parse object-literal-generator
+ "var x = {*foo() { yield 42;\n}};")
+
+(js2-deftest-parse object-literal-computed-generator-key
+ "var x = {*[foo + bar]() { yield 42;\n}};")
+
;;; Function definition
(js2-deftest function-redeclaring-var "var gen = 3; function gen() {};"
@@ -235,6 +247,59 @@ the test."
(js2-deftest-parse function-with-rest-after-default-parameter
"function foo(a = 1, ...rest) {\n}")
+;;; Strict mode errors
+
+(js2-deftest-parse function-bad-strict-parameters
+ "'use strict';\nfunction foo(eval, {arguments}, bar) {\n}"
+ :syntax-error "eval" :errors-count 2)
+
+(js2-deftest-parse function-retroactive-bad-strict-parameters
+ "function foo(arguments) {'use strict';}"
+ :syntax-error "arguments" :errors-count 1)
+
+(js2-deftest-parse function-duplicate-strict-parameters
+ "'use strict';\nfunction foo(a, a) {\n}"
+ :syntax-error "a" :errors-count 1)
+
+(js2-deftest-parse function-bad-strict-function-name
+ "'use strict';\nfunction eval() {\n}"
+ :syntax-error "eval" :errors-count 1)
+
+(js2-deftest-parse function-bad-retroactive-strict-function-name
+ "function arguments() {'use strict';}"
+ :syntax-error "arguments" :errors-count 1)
+
+(js2-deftest-parse function-bad-strict-catch-name
+ "'use strict';\ntry {} catch (eval) {}"
+ :syntax-error "eval" :errors-count 1)
+
+(js2-deftest-parse function-bad-strict-variable-name
+ "'use strict';\nvar eval = 'kekeke';"
+ :syntax-error "eval" :errors-count 1)
+
+(js2-deftest-parse function-bad-strict-assignment
+ "'use strict';\narguments = 'fufufu';"
+ :syntax-error "arguments" :errors-count 1)
+
+(js2-deftest-parse function-property-strict-assignment
+ "'use strict';\narguments.okay = 'alright';")
+
+(js2-deftest-parse function-strict-with
+ "'use strict';\nwith ({}) {}"
+ :syntax-error "with" :errors-count 1)
+
+(js2-deftest-parse function-strict-octal
+ "'use strict';\nvar number = 0644;"
+ :syntax-error "0644" :errors-count 1)
+
+(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-const-scope
+ "'use strict';\nconst a;\nif (1) {\n const a;\n}")
+
;;; Spread operator
(js2-deftest-parse spread-in-array-literal
@@ -709,19 +774,49 @@ the test."
(should (= (js2-symbol-decl-type var-entry) js2-VAR))
(should (js2-name-node-p (js2-symbol-ast-node var-entry)))))
-(js2-deftest for-node-is-declaration-scope "for (let i = 0; i; ++i) {};"
- (js2-mode)
- (search-forward "i")
+(defun js2-test-scope-of-nth-variable-satisifies-predicate (variable nth
predicate)
+ (goto-char (point-min))
+ (dotimes (n (1+ nth)) (search-forward variable))
(forward-char -1)
(let ((scope (js2-node-get-enclosing-scope (js2-node-at-point))))
- (should (js2-for-node-p (js2-get-defining-scope scope "i")))))
+ (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-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-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-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)
- (search-forward "x")
- (forward-char -1)
- (let ((scope (js2-node-get-enclosing-scope (js2-node-at-point))))
- (should (js2-comp-loop-node-p (js2-get-defining-scope scope "x")))))
+ (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)
+ (search-forward "for")
+ (forward-char -3)
+ (let ((node (js2-node-at-point)))
+ (should (js2-scope-parent-scope node))
+ (should (js2-get-defining-scope node "j"))))
;;; Tokenizer
@@ -805,3 +900,139 @@ the test."
(js2-mode)
(let ((node (js2-node-at-point (point-min))))
(should (= (js2-node-len node) 3))))
+
+;;; Variables classification
+
+(defun js2--variables-summary (vars)
+ (let (r)
+ (setq vars (let (aslist)
+ (maphash (lambda (k v) (push (cons k v) aslist)) vars)
+ aslist))
+ (dolist (v (sort vars (lambda (a b) (< (js2-node-abs-pos
(js2-symbol-ast-node (car a)))
+ (js2-node-abs-pos (js2-symbol-ast-node
(car b)))))))
+ (let* ((symbol (car v))
+ (inition (cadr v))
+ (uses (cddr v))
+ (symn (js2-symbol-ast-node symbol))
+ (namen (js2--get-name-node symn)))
+ (push (format "address@hidden:%s"
+ (js2-symbol-name symbol)
+ (js2-node-abs-pos namen)
+ (if (eq inition ?P)
+ "P"
+ (if uses
+ (if inition "I" "N")
+ "U"))) r)
+ (dolist (u (sort (cddr v) (lambda (a b) (< (js2-node-abs-pos a)
+ (js2-node-abs-pos b)))))
+ (push (js2-node-abs-pos u) r))))
+ (reverse r)))
+
+(defmacro js2-deftest-classify-variables (name buffer-contents summary)
+ (declare (indent defun))
+ `(ert-deftest ,(intern (format "js2-classify-variables-%s" name)) ()
+ (with-temp-buffer
+ (save-excursion
+ (insert ,buffer-contents))
+ (unwind-protect
+ (progn
+ (js2-mode)
+ (should (equal ,summary (js2--variables-summary
+ (js2--classify-variables)))))
+ (fundamental-mode)))))
+
+(js2-deftest-classify-variables incomplete-var-statement
+ "var"
+ '())
+
+(js2-deftest-classify-variables unused-variable
+ "function foo () { var x; return 42; }"
+ '("address@hidden:U" "address@hidden:U"))
+
+(js2-deftest-classify-variables unused-variable-declared-twice
+ "function foo (a) { var x; function bar () { var x; x=42; }; return a;}"
+ '("address@hidden:U" "address@hidden:P" 68 "address@hidden:U"
"address@hidden:U" "address@hidden:U"))
+
+(js2-deftest-classify-variables assigned-variable
+ "function foo () { var x; x=42; return x; }"
+ '("address@hidden:U" "address@hidden:I" 39))
+
+(js2-deftest-classify-variables assignment-in-nested-function
+ "function foo () { var x; function bar () { x=42; }; }"
+ '("address@hidden:U" "address@hidden:U" "address@hidden:U"))
+
+(js2-deftest-classify-variables unused-nested-function
+ "function foo() { var i, j=1; function bar() { var x, y=42, z=i; return y; }
return i; }"
+ '("address@hidden:U" "address@hidden:N" 62 84 "address@hidden:U"
"address@hidden:U" "address@hidden:U" "address@hidden:I" 72 "address@hidden:U"))
+
+(js2-deftest-classify-variables prop-get-initialized
+ "function foo () { var x, y={}; y.a=x; }"
+ '("address@hidden:U" "address@hidden:N" 36 "address@hidden:I" 32))
+
+(js2-deftest-classify-variables prop-get-uninitialized
+ "function foo () { var x; if(x.foo) alert('boom'); }"
+ '("address@hidden:U" "address@hidden:N" 29))
+
+(js2-deftest-classify-variables prop-get-function-assignment
+ "(function(w) { w.f = function() { var a=42, m; return a; }; })(window);"
+ '("address@hidden:P" 11 16 "address@hidden:I" 55 "address@hidden:U"))
+
+(js2-deftest-classify-variables let-declaration
+ "function foo () { let x,y=1; return x; }"
+ '("address@hidden:U" "address@hidden:N" 37 "address@hidden:U"))
+
+(js2-deftest-classify-variables external-function-call
+ "function foo (m) { console.log(m, arguments); }"
+ '("address@hidden:U" "address@hidden:P" 32))
+
+(js2-deftest-classify-variables global-function-call
+ "function bar () { return 42; } function foo (a) { return bar(); }"
+ '("address@hidden:I" 58 "address@hidden:U" "address@hidden:P"))
+
+(js2-deftest-classify-variables let-declaration-for-scope
+ "function foo () { for(let x=1,y; x<y; y++) {} }"
+ '("address@hidden:U" "address@hidden:I" 34 "address@hidden:N" 36 39))
+
+(js2-deftest-classify-variables arguments-implicit-var
+ "function foo () { var p; for(p in arguments) { return p; } }"
+ '("address@hidden:U" "address@hidden:I" 55))
+
+(js2-deftest-classify-variables catch-error-variable
+ "function foo () { try { throw 'Foo'; } catch (e) { console.log(e); }"
+ '("address@hidden:U" "address@hidden:I" 64))
+
+(js2-deftest-classify-variables prop-get-assignment
+ "function foo () { var x={y:{z:{}}}; x.y.z=42; }"
+ '("address@hidden:U" "address@hidden:I" 37))
+
+(js2-deftest-classify-variables unused-function-argument
+ "function foo (a) { return 42; }"
+ '("address@hidden:U" "address@hidden:P"))
+
+(js2-deftest-classify-variables used-function-argument
+ "function foo (a) { a=42; return a; }"
+ '("address@hidden:U" "address@hidden:P" 33))
+
+(js2-deftest-classify-variables prop-get
+ "function foo (a) { a=navigator.x||navigator.y; return a; }"
+ '("address@hidden:U" "address@hidden:P" 55))
+
+(js2-deftest-classify-variables for-in-loop
+ "function foo () { var d={}; for(var k in d) {var v=d[k]; } }"
+ '("address@hidden:U" "address@hidden:I" 42 52 "address@hidden:I" 54
"address@hidden:U"))
+
+(js2-deftest-classify-variables array-comprehension-legacy
+ "function foo() { var j,a=[for (i of [1,2,3]) i*j]; }"
+ '("address@hidden:U" "address@hidden:N" 48 "address@hidden:U"
"address@hidden:I" 46))
+
+(js2-deftest-classify-variables array-comprehension
+ "function foo() { var j,a=[[i,j] for (i of [1,2,3])]; }"
+ '("address@hidden:U" "address@hidden:N" 30 "address@hidden:U"
"address@hidden:I" 28))
+
+(js2-deftest-classify-variables return-named-function
+ "function foo() { var a=42; return function bar() { return a; } }"
+ '("address@hidden:U" "address@hidden:I" 59 "address@hidden:I" 44))
+
+(js2-deftest-classify-variables named-wrapper-function
+ "function foo() { var a; (function bar() { a=42; })(); return a; }"
+ '("address@hidden:U" "address@hidden:I" 62 "address@hidden:I" 35))
- [elpa] master f1edac7 19/33: Don't treat a block-scoped const as redeclaration, (continued)
- [elpa] master f1edac7 19/33: Don't treat a block-scoped const as redeclaration, Dmitry Gutov, 2015/07/12
- [elpa] master c588e31 17/33: Duplicate object keys are an error in strict mode, Dmitry Gutov, 2015/07/12
- [elpa] master 2469440 26/33: Fix bug where properties where checked for strict mode compliance, Dmitry Gutov, 2015/07/12
- [elpa] master 12e5a21 28/33: Remove js2-current-indent, Dmitry Gutov, 2015/07/12
- [elpa] master 5b25da2 30/33: Add NEWS entry and bump version, Dmitry Gutov, 2015/07/12
- [elpa] master e0ecf99 14/33: Check identifiers in strict mode., Dmitry Gutov, 2015/07/12
- [elpa] master f3a899b 32/33: And actually update NEWS, Dmitry Gutov, 2015/07/12
- [elpa] master 53f3f52 31/33: Actually remove the macro, Dmitry Gutov, 2015/07/12
- [elpa] master eb33e68 29/33: Get rid of js2-with-underscore-as-word-syntax, Dmitry Gutov, 2015/07/12
- [elpa] master 5e5df54 27/33: Merge pull request #242 from jacksonrayhamilton/strict-mode, Dmitry Gutov, 2015/07/12
- [elpa] master 2763c9a 33/33: Merge commit 'f3a899bb1c36e25e078eed2890eb2fecb22f4175' from js2-mode,
Dmitry Gutov <=