;; Dmitry Gutov <dgutov@yandex.ru>
;; URL: https://github.com/mooz/js2-mode/
;; http://code.google.com/p/js2-mode/
-;; Version: 20150713
+;; Version: 20150909
;; Keywords: languages, javascript
;; Package-Requires: ((emacs "24.1") (cl-lib "0.5"))
"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.
(define-key map (kbd "C-c C-o") #'js2-mode-toggle-element)
(define-key map (kbd "C-c C-w") #'js2-mode-toggle-warnings-and-errors)
(define-key map [down-mouse-3] #'js2-down-mouse-3)
- (define-key map (kbd "M-.") #'js2-jump-to-definition)
+ (define-key map [remap js-find-symbol] #'js2-jump-to-definition)
(define-key map [menu-bar javascript]
(cons "JavaScript" (make-sparse-keymap "JavaScript")))
(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))
(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)
+ (unless (or method arrow)
(insert pad "function")
(when (eq (js2-function-node-generator-type n) 'STAR)
(insert "*")))
(js2-print-list (js2-object-node-elems n))
(insert "}"))
+(cl-defstruct (js2-computed-prop-name-node
+ (:include js2-node)
+ (:constructor nil)
+ (: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-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-computed-prop-name-node (n v)
+ (js2-visit-ast (js2-computed-prop-name-node-expr n) v))
+
+(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)
(:constructor nil)
(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 type ; GET, SET, or FUNCTION
+ (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 js2-GET, js2-SET, or js2-FUNCTION. ")
-(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)))
(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
(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
(list node)))
((js2-object-node-p node)
(dolist (elem (js2-object-node-elems node))
- (when (js2-object-prop-node-p elem)
+ ;; js2-infix-node-p catches both object prop node and initialized
+ ;; binding element (which is directly an infix node).
+ (when (js2-infix-node-p elem)
(push (js2-define-destruct-symbols
- ;; In abbreviated destructuring {a, b}, right == left.
- (js2-object-prop-node-right elem)
+ (js2-infix-node-left elem)
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
+ (if (js2-infix-node-p elem) (setq elem (js2-infix-node-left elem)))
(push (js2-define-destruct-symbols
elem decl-type face ignore-not-in-block)
name-nodes)))
(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.
(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
(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)
(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
+ (push (cond
+ ((and (= tt js2-NAME)
+ (= js2-ASSIGN (js2-peek-token)))
+ ;; a=defaultValue
+ (js2-parse-initialized-binding (js2-parse-name js2-NAME)))
+ ((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
+ (t
(js2-report-error "msg.bad.var")
- (make-js2-error-node))
+ (make-js2-error-node)))
elems)
(setq after-lb-or-comma nil
after-comma nil))
(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)))
+ (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))
(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
(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)
+ (/= (js2-method-node-type elem)
+ (js2-method-node-type previous-elem))))))
elems))
(js2-report-error "msg.dup.obj.lit.prop.strict"
elem-key-string
(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 ((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-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))
+ (js2-parse-initialized-binding key))
;; regular prop
(t
(let ((beg (js2-current-token-beg))
(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,
(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(...) {...} }
and expression closure style is also supported
(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
+ (js2-node-set-prop fn 'METHOD_TYPE 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
- :len (- end pos)
- :left prop
- :right fn))
+ result (make-js2-method-node :type type
+ :pos pos
+ :len (- end pos)
+ :left prop
+ :right fn))
(js2-node-add-children result prop fn)
result))
(ring-insert find-tag-marker-ring (point-marker))
(let* ((node (js2-node-at-point))
(parent (js2-node-parent node))
- (names (if (js2-prop-get-node-p parent)(reverse (js2-names-left node))))
+ (names (if (js2-prop-get-node-p parent)
+ (reverse (let ((temp (js2-compute-nested-prop-get parent)))
+ (cl-loop for n in temp
+ with result = '()
+ do (push n result)
+ until (equal node n)
+ finally return result)))))
node-init)
(unless (and (js2-name-node-p node)
- (not (js2-var-init-node-p parent))
- (not (js2-function-node-p parent)))
+ (not (js2-var-init-node-p parent))
+ (not (js2-function-node-p parent)))
(error "Node is not a supported jump node"))
(push (or (and names (pop names))
- (unless (and (js2-object-prop-node-p parent)
- (eq node (js2-object-prop-node-left parent)))
- (js2-name-node-name node))) names)
+ (unless (and (js2-object-prop-node-p parent)
+ (eq node (js2-object-prop-node-left parent)))
+ node)) names)
(setq node-init (js2-search-scope node names))
;; todo: display list of results in buffer
(throw 'found b)))))
(buffer-list)))
nil)))
- (setq node-init (if (listp node-init) (first node-init) node-init))
+ (setq node-init (if (listp node-init) (car node-init) node-init))
(unless node-init
(pop-tag-mark)
(error "No jump location found"))
(goto-char (js2-node-abs-pos node-init))))
-(defun js2-build-prop-name-list (prop-node)
- "Build a list of names from a PROP-NODE."
- (let* (names
- left
- left-node)
- (unless (js2-prop-get-node-p prop-node)
- (error "Node is not a property prop-node"))
- (while (js2-prop-get-node-p prop-node)
- (let ((node (js2-prop-get-node-right prop-node)))
- (push `(,(js2-name-node-name node) . ,node) names)
- (setq left-node (js2-prop-get-node-left prop-node))
- (when (js2-name-node-p left-node)
- (setq left `(,(js2-name-node-name left-node) . ,left-node)))
- (setq prop-node left-node)))
- (push left names)))
-
-(defun js2-search-object (node name)
- "Check if object NODE contains element with NAME."
- (unless (js2-object-node-p node)
- (error "Only run depth search on `js2-object-node'"))
- ;; Only support name and nodes for the time being
+(defun js2-search-object (node name-node)
+ "Check if object NODE contains element with NAME-NODE."
+ (cl-assert (js2-object-node-p node))
+ ;; Only support name-node and nodes for the time being
(cl-loop for elem in (js2-object-node-elems node)
for left = (js2-object-prop-node-left elem)
if (or (and (js2-name-node-p left)
- (string= name (js2-name-node-name left)))
- (and (js2-string-node-p left)
- (string= name (js2-string-node-value left))))
+ (equal (js2-name-node-name name-node)
+ (js2-name-node-name left)))
+ (and (js2-string-node-p left)
+ (string= (js2-name-node-name name-node)
+ (js2-string-node-value left))))
return elem))
(defun js2-search-object-for-prop (object prop-names)
(while (and temp names (js2-object-node-p temp-object))
(setq temp (js2-search-object temp-object (pop names)))
(and (setq node temp)
- (setq temp-object (js2-object-prop-node-right temp) )))
+ (setq temp-object (js2-object-prop-node-right temp))))
(unless names node)))
(defun js2-search-scope (node names)
- "Searches SCOPE for jump location in NAMES."
+ "Searches NODE scope for jump location matching NAMES.
+NAMES is a list of property values to search for. For functions
+and variables NAMES will contain one element."
(let (node-init
- (val (first names)))
+ (val (js2-name-node-name (car names))))
(setq node-init (js2-get-symbol-declaration node val))
(when (> (length names) 1)
(temp-names names))
(pop temp-names) ;; First element is var name
(setq node-init (when (js2-var-init-node-p parent)
- (js2-search-object-for-prop (js2-var-init-node-initializer parent)
- temp-names)))))
+ (js2-search-object-for-prop
+ (js2-var-init-node-initializer parent)
+ temp-names)))))
;; Check all assign nodes
(js2-visit-ast
(right (js2-assign-node-right node))
(temp-names names))
(when (js2-prop-get-node-p left)
- (let* ((prop-list (js2-build-prop-name-list left))
- (prop-names (mapcar 'car prop-list))
- (found (loop for prop in prop-names
- until (not (string= (pop temp-names) prop))
- if (not temp-names) return prop)))
-
- ;; todo: clean this up!
- (if found (push (cdr (assoc found prop-list)) node-init)
- (when (js2-object-node-p right)
- (setq found (js2-search-object-for-prop right temp-names)))
- (if found (push found node-init))))))
- t)))))
+ (let* ((prop-list (js2-compute-nested-prop-get left))
+ (found (cl-loop for prop in prop-list
+ until (not (string= (js2-name-node-name
+ (pop temp-names))
+ (js2-name-node-name prop)))
+ if (not temp-names) return prop))
+ (found-node (or found
+ (when (js2-object-node-p right)
+ (js2-search-object-for-prop right
+ temp-names)))))
+ (if found-node (push found-node node-init))))))
+ t))))
node-init))
-(defun js2-names-left (name-node)
-"Returns a list of names for a `js2-prop-get-node'.
-NAME-NODE is a node that forms part of the `js2-prop-get-node'."
- (let* (name
- (parent (js2-node-parent name-node))
- left
- names)
- (unless (js2-prop-get-node-p parent)
- (error "Parent is not a prop-get-node"))
- (setq name (js2-name-node-name name-node)
- left (js2-prop-get-node-left parent))
- (if (and (js2-name-node-p left)
- (string= name (js2-name-node-name left)))
- (setq names name)
- (js2-visit-ast
- parent
- (lambda (node endp)
- (unless endp
- (if (js2-name-node-p node)
- (push (js2-name-node-name node) names)
- t))))
- names)
- (if (listp names) names (list names))))
-
(defun js2-get-symbol-declaration (node name)
"Find scope for NAME from NODE."
(let ((scope (js2-get-defining-scope