;; Copyright (C) 2009 Free Software Foundation, Inc.
;; Author: Steve Yegge <steve.yegge@gmail.com>
-;; mooz <stillpedant@gmail.com>
+;; Contributors: mooz <stillpedant@gmail.com>
+;; Dmitry Gutov <dgutov@yandex.ru>
;; Version: See `js2-mode-version'
;; Keywords: languages, javascript
UserDataHandler
;; Window
- alert confirm document java navigator prompt screen
+ window alert confirm document java navigator prompt screen
self top
;; W3C CSS
;;; Variables
+(defun js2-mark-safe-local (name pred)
+ "Make the variable NAME buffer-local and mark it as safe file-local
+variable with predicate PRED."
+ (make-variable-buffer-local name)
+ (put name 'safe-local-variable pred))
+
(defvar js2-emacs22 (>= emacs-major-version 22))
(defcustom js2-highlight-level 2
Similar to `c-basic-offset'."
:group 'js2-mode
:type 'integer)
-(make-variable-buffer-local 'js2-basic-offset)
+(js2-mark-safe-local 'js2-basic-offset 'integerp)
;; TODO(stevey): move this code into a separate minor mode.
(defcustom js2-mirror-mode nil
already indented to that predetermined column, indenting will choose
another likely column and indent to that spot. Repeated invocation of
the indent-line function will cycle among the computed alternatives.
-See the function `js2-bounce-indent' for details."
+See the function `js2-bounce-indent' for details. When it is non-nil,
+js2-mode also binds `js2-bounce-indent-backwards' to Shift-Tab."
:type 'boolean
:group 'js2-mode)
regardless of the beginning bracket position."
:group 'js2-mode
:type 'boolean)
+(js2-mark-safe-local 'js2-consistent-level-indent-inner-bracket-p 'booleanp)
+
+(defcustom js2-pretty-multiline-decl-indentation-p t
+ "Non-nil to line up multiline declarations vertically. See the
+function `js-multiline-decl-indentation' for details."
+ :group 'js2-mode
+ :type 'boolean)
+(js2-mark-safe-local 'js2-pretty-multiline-decl-indentation-p 'booleanp)
+
+(defcustom js2-always-indent-assigned-expr-in-decls-p nil
+ "If both `js2-pretty-multiline-decl-indentation-p' and this are non-nil,
+always additionally indent function expression or array/object literal
+assigned in a declaration, even when only one var is declared."
+ :group 'js2-mode
+ :type 'boolean)
+(js2-mark-safe-local 'js2-always-indent-assigned-expr-in-decls-p 'booleanp)
(defcustom js2-indent-on-enter-key nil
"Non-nil to have Enter/Return key indent the line.
(js2-deflocal js2-nesting-of-function 0)
-(js2-deflocal js2-recorded-assignments nil
- "Tracks assignments found during parsing.")
+(js2-deflocal js2-recorded-identifiers nil
+ "Tracks identifiers found during parsing.")
(defmacro js2-in-lhs (body)
`(let ((js2-is-in-lhs t))
(defcustom js2-post-parse-callbacks nil
"A list of callback functions invoked after parsing finishes.
Currently, the main use for this function is to add synthetic
-declarations to `js2-recorded-assignments', which see."
+declarations to `js2-recorded-identifiers', which see."
:type 'list
:group 'js2-mode)
(defface js2-external-variable-face
'((t :foreground "orange"))
- "Face used to highlight assignments to undeclared variables.
+ "Face used to highlight undeclared variable identifiers.
An undeclared variable is any variable not declared with var or let
-in the current scope or any lexically enclosing scope. If you assign
-to such a variable, then you are either expecting it to originate from
+in the current scope or any lexically enclosing scope. If you use
+such a variable, then you are either expecting it to originate from
another file, or you've got a potential bug."
:group 'js2-mode)
(defcustom js2-highlight-external-variables t
- "Non-nil to higlight assignments to undeclared variables."
+ "Non-nil to highlight undeclared variable identifiers."
:type 'boolean
:group 'js2-mode)
(mapc (lambda (key)
(define-key map key #'js2-insert-and-indent))
js2-electric-keys))
+ (when js2-bounce-indent-p
+ (define-key map (kbd "<backtab>") #'js2-indent-bounce-backwards))
(define-key map [menu-bar javascript]
(cons "JavaScript" (make-sparse-keymap "JavaScript")))
,delta)
,form
(/ (truncate (* (- (float-time (current-time))
- (float-time ,beg)))
- 10000)
+ (float-time ,beg))
+ 10000))
10000.0))))
(defsubst js2-same-line (pos)
"Signal an error when we encounter an unexpected code path."
(error "failed assertion"))
+(defsubst js2-record-text-property (beg end prop value)
+ "Record a text property to set when parsing finishes."
+ (push (list beg end prop value) js2-mode-deferred-properties))
+
;; I'd like to associate errors with nodes, but for now the
;; easiest thing to do is get the context info from the last token.
(defsubst js2-record-parse-error (msg &optional arg pos len)
The node traversal is approximately lexical-order, although there
are currently no guarantees around this."
- (let ((vfunc (get (aref node 0) 'js2-visitor)))
- ;; visit the node
- (when (funcall callback node nil)
- ;; visit the kids
- (cond
- ((eq vfunc 'js2-visit-none)
- nil) ; don't even bother calling it
- ;; Each AST node type has to define a `js2-visitor' function
- ;; that takes a node and a callback, and calls `js2-visit-ast'
- ;; on each child of the node.
- (vfunc
- (funcall vfunc node callback))
- (t
- (error "%s does not define a visitor-traversal function"
- (aref node 0)))))
- ;; call the end-visit
- (funcall callback node t)))
+ (if node
+ (let ((vfunc (get (aref node 0) 'js2-visitor)))
+ ;; visit the node
+ (when (funcall callback node nil)
+ ;; visit the kids
+ (cond
+ ((eq vfunc 'js2-visit-none)
+ nil) ; don't even bother calling it
+ ;; Each AST node type has to define a `js2-visitor' function
+ ;; that takes a node and a callback, and calls `js2-visit-ast'
+ ;; on each child of the node.
+ (vfunc
+ (funcall vfunc node callback))
+ (t
+ (error "%s does not define a visitor-traversal function"
+ (aref node 0)))))
+ ;; call the end-visit
+ (funcall callback node t))))
(defstruct (js2-node
(:constructor nil)) ; abstract
(put 'cl-struct-js2-return-node 'js2-printer 'js2-print-return-node)
(defun js2-visit-return-node (n v)
- (if (js2-return-node-retval n)
- (js2-visit-ast (js2-return-node-retval n) v)))
+ (js2-visit-ast (js2-return-node-retval n) v))
(defun js2-print-return-node (n i)
(insert (js2-make-pad i) "return")
(defun js2-visit-if-node (n v)
(js2-visit-ast (js2-if-node-condition n) v)
(js2-visit-ast (js2-if-node-then-part n) v)
- (if (js2-if-node-else-part n)
- (js2-visit-ast (js2-if-node-else-part n) v)))
+ (js2-visit-ast (js2-if-node-else-part n) v))
(defun js2-print-if-node (n i)
(let ((pad (js2-make-pad i))
(js2-visit-ast (js2-try-node-try-block n) v)
(dolist (clause (js2-try-node-catch-clauses n))
(js2-visit-ast clause v))
- (if (js2-try-node-finally-block n)
- (js2-visit-ast (js2-try-node-finally-block n) v)))
+ (js2-visit-ast (js2-try-node-finally-block n) v))
(defun js2-print-try-node (n i)
(let ((pad (js2-make-pad i))
(:constructor make-js2-catch-node (&key (type js2-CATCH)
(pos js2-ts-cursor)
len
- var-name
+ param
guard-kwd
guard-expr
block
lp
rp)))
"AST node for a catch clause."
- var-name ; a `js2-name-node'
+ param ; destructuring form or simple name node
guard-kwd ; relative buffer position of "if" in "catch (x if ...)"
guard-expr ; catch condition, a `js2-node'
block ; statements, a `js2-block-node'
(put 'cl-struct-js2-catch-node 'js2-printer 'js2-print-catch-node)
(defun js2-visit-catch-node (n v)
- (js2-visit-ast (js2-catch-node-var-name n) v)
+ (js2-visit-ast (js2-catch-node-param n) v)
(when (js2-catch-node-guard-kwd n)
(js2-visit-ast (js2-catch-node-guard-expr n) v))
(js2-visit-ast (js2-catch-node-block n) v))
(guard-kwd (js2-catch-node-guard-kwd n))
(guard-expr (js2-catch-node-guard-expr n)))
(insert " catch (")
- (js2-print-ast (js2-catch-node-var-name n) 0)
+ (js2-print-ast (js2-catch-node-param n) 0)
(when guard-kwd
(insert " if ")
(js2-print-ast guard-expr 0))
(put 'cl-struct-js2-case-node 'js2-printer 'js2-print-case-node)
(defun js2-visit-case-node (n v)
- (if (js2-case-node-expr n) ; nil for default: case
- (js2-visit-ast (js2-case-node-expr n) v))
+ (js2-visit-ast (js2-case-node-expr n) v)
(js2-visit-block n v))
(defun js2-print-case-node (n i)
target) ; target js2-labels-node or loop/switch statement
(defun js2-visit-jump-node (n v)
- ;; we don't visit the target, since it's a back-link
- (if (js2-jump-node-label n)
- (js2-visit-ast (js2-jump-node-label n) v)))
+ (js2-visit-ast (js2-jump-node-label n) v))
(defstruct (js2-break-node
(:include js2-jump-node)
(put 'cl-struct-js2-function-node 'js2-printer 'js2-print-function-node)
(defun js2-visit-function-node (n v)
- (if (js2-function-node-name n)
- (js2-visit-ast (js2-function-node-name n) v))
+ (js2-visit-ast (js2-function-node-name n) v)
(dolist (p (js2-function-node-params n))
(js2-visit-ast p v))
- (when (js2-function-node-body n)
- (js2-visit-ast (js2-function-node-body n) v)))
+ (js2-visit-ast (js2-function-node-body n) v))
(defun js2-print-function-node (n i)
(let ((pad (js2-make-pad i))
(defun js2-visit-var-init-node (n v)
(js2-visit-ast (js2-var-init-node-target n) v)
- (if (js2-var-init-node-initializer n)
- (js2-visit-ast (js2-var-init-node-initializer n) v)))
+ (js2-visit-ast (js2-var-init-node-initializer n) v))
(defun js2-print-var-init-node (n i)
(let ((pad (js2-make-pad i))
(put 'cl-struct-js2-infix-node 'js2-printer 'js2-print-infix-node)
(defun js2-visit-infix-node (n v)
- (when (js2-infix-node-left n)
- (js2-visit-ast (js2-infix-node-left n) v))
- (when (js2-infix-node-right n)
- (js2-visit-ast (js2-infix-node-right n) v)))
+ (js2-visit-ast (js2-infix-node-left n) v)
+ (js2-visit-ast (js2-infix-node-right n) v))
(defconst js2-operator-tokens
(let ((table (make-hash-table :test 'eq))
(put 'cl-struct-js2-let-node 'js2-printer 'js2-print-let-node)
(defun js2-visit-let-node (n v)
- (when (js2-let-node-vars n)
- (js2-visit-ast (js2-let-node-vars n) v))
- (when (js2-let-node-body n)
- (js2-visit-ast (js2-let-node-body n) v)))
+ (js2-visit-ast (js2-let-node-vars n) v)
+ (js2-visit-ast (js2-let-node-body n) v))
(defun js2-print-let-node (n i)
(insert (js2-make-pad i) "let (")
(js2-visit-ast (js2-new-node-target n) v)
(dolist (arg (js2-new-node-args n))
(js2-visit-ast arg v))
- (when (js2-new-node-initializer n)
- (js2-visit-ast (js2-new-node-initializer n) v)))
+ (js2-visit-ast (js2-new-node-initializer n) v))
(defun js2-print-new-node (n i)
(insert (js2-make-pad i) "new ")
(defun js2-visit-array-node (n v)
(dolist (e (js2-array-node-elems n))
- (when e ; can be nil, e.g. [a, ,b]
- (js2-visit-ast e v))))
+ (js2-visit-ast e v)))
(defun js2-print-array-node (n i)
(insert (js2-make-pad i) "[")
(pos js2-ts-cursor)
len
elems)))
- "AST node for an object literal expression."
- elems) ; a lisp list of `js2-object-prop-node'
+ "AST node for an object literal expression.
+`elems' is a list of either `js2-object-prop-node' or `js2-name-node',
+the latter represents abbreviation in destructuring expressions."
+ 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)
(put 'cl-struct-js2-prop-get-node 'js2-printer 'js2-print-prop-get-node)
(defun js2-visit-prop-get-node (n v)
- (when (js2-prop-get-node-left n)
- (js2-visit-ast (js2-prop-get-node-left n) v))
- (when (js2-prop-get-node-right n)
- (js2-visit-ast (js2-prop-get-node-right n) v)))
+ (js2-visit-ast (js2-prop-get-node-left n) v)
+ (js2-visit-ast (js2-prop-get-node-right n) v))
(defun js2-print-prop-get-node (n i)
(insert (js2-make-pad i))
(put 'cl-struct-js2-elem-get-node 'js2-printer 'js2-print-elem-get-node)
(defun js2-visit-elem-get-node (n v)
- (when (js2-elem-get-node-target n)
- (js2-visit-ast (js2-elem-get-node-target n) v))
- (when (js2-elem-get-node-element n)
- (js2-visit-ast (js2-elem-get-node-element n) v)))
+ (js2-visit-ast (js2-elem-get-node-target n) v)
+ (js2-visit-ast (js2-elem-get-node-element n) v))
(defun js2-print-elem-get-node (n i)
(insert (js2-make-pad i))
(js2-visit-ast (js2-array-comp-node-result n) v)
(dolist (l (js2-array-comp-node-loops n))
(js2-visit-ast l v))
- (if (js2-array-comp-node-filter n)
- (js2-visit-ast (js2-array-comp-node-filter n) v)))
+ (js2-visit-ast (js2-array-comp-node-filter n) v))
(defun js2-print-array-comp-node (n i)
(let ((pad (js2-make-pad i))
(put 'cl-struct-js2-xml-prop-ref-node 'js2-printer 'js2-print-xml-prop-ref-node)
(defun js2-visit-xml-prop-ref-node (n v)
- (if (js2-xml-prop-ref-node-namespace n)
- (js2-visit-ast (js2-xml-prop-ref-node-namespace n) v))
- (if (js2-xml-prop-ref-node-propname n)
- (js2-visit-ast (js2-xml-prop-ref-node-propname n) v)))
+ (js2-visit-ast (js2-xml-prop-ref-node-namespace n) v)
+ (js2-visit-ast (js2-xml-prop-ref-node-propname n) v))
(defun js2-print-xml-prop-ref-node (n i)
(insert (js2-make-pad i))
(put 'cl-struct-js2-xml-elem-ref-node 'js2-printer 'js2-print-xml-elem-ref-node)
(defun js2-visit-xml-elem-ref-node (n v)
- (if (js2-xml-elem-ref-node-namespace n)
- (js2-visit-ast (js2-xml-elem-ref-node-namespace n) v))
- (if (js2-xml-elem-ref-node-expr n)
- (js2-visit-ast (js2-xml-elem-ref-node-expr n) v)))
+ (js2-visit-ast (js2-xml-elem-ref-node-namespace n) v)
+ (js2-visit-ast (js2-xml-elem-ref-node-expr n) v))
(defun js2-print-xml-elem-ref-node (n i)
(insert (js2-make-pad i))
(defsubst js2-nested-function-p (node)
"Return t if NODE is a nested function, or is inside a nested function."
- (js2-function-node-p (if (js2-function-node-p node)
- (js2-node-parent-script-or-fn node)
- (js2-node-parent-script-or-fn
- (js2-node-parent-script-or-fn node)))))
+ (unless (js2-ast-root-p node)
+ (js2-function-node-p (if (js2-function-node-p node)
+ (js2-node-parent-script-or-fn node)
+ (js2-node-parent-script-or-fn
+ (js2-node-parent-script-or-fn node))))))
(defsubst js2-function-param-node-p (node)
"Return non-nil if NODE is a param node of a `js2-function-node'."
;; I'll wait for people to notice incorrect warnings.
((and (= tt js2-EXPR_VOID)
(js2-expr-stmt-node-p node)) ; but not if EXPR_RESULT
- (js2-node-has-side-effects (js2-expr-stmt-node-expr node)))
+ (let ((expr (js2-expr-stmt-node-expr node)))
+ (or (js2-node-has-side-effects expr)
+ (when (js2-string-node-p expr)
+ (string= "use strict" (js2-string-node-value expr))))))
((= tt js2-COMMA)
(js2-node-has-side-effects (js2-infix-node-right node)))
((or (= tt js2-AND)
js2-ts-regexp-flags (js2-collect-string flags)
js2-token-end js2-ts-cursor)
;; tell `parse-partial-sexp' to ignore this range of chars
- (put-text-property js2-token-beg js2-token-end 'syntax-class '(2)))))
+ (js2-record-text-property js2-token-beg js2-token-end 'syntax-class '(2)))))
(defun js2-get-first-xml-token ()
(setq js2-ts-xml-open-tags-count 0
(throw 'return js2-ERROR)))
(t
(unless (js2-read-entity)
- (throw 'return js2-ERROR)))))
+ (throw 'return js2-ERROR))))
+ ;; allow bare CDATA section
+ ;; ex) let xml = <![CDATA[ foo bar baz ]]>;
+ (when (zerop js2-ts-xml-open-tags-count)
+ (throw 'return js2-XMLEND)))
(??
(setq c (js2-get-char)) ;; skip ?
(js2-add-to-string c)
point-entered nil
c-in-sws nil)))
-(defsubst js2-record-text-property (beg end prop value)
- "Record a text property to set when parsing finishes."
- (push (list beg end prop value) js2-mode-deferred-properties))
-
(defconst js2-ecma-global-props
(concat "^"
(regexp-opt
"\\(?:param\\|argument\\)"
"\\)"
"\\s-*\\({[^}]+}\\)?" ; optional type
- "\\s-*\\([a-zA-Z0-9_$]+\\)?" ; name
+ "\\s-*\\[?\\([a-zA-Z0-9_$]+\\)?\\]?" ; name
"\\>")
"Matches jsdoc tags with optional type and optional param name.")
(js2-set-face (setq leftpos (js2-node-abs-pos name))
(+ leftpos (js2-node-len name))
'font-lock-function-name-face
- 'record)))
- ;; save variable assignments so we can check for undeclared later
- ;; (can't do it here since var decls can come at end of script)
- (when (and js2-highlight-external-variables
- (setq name (js2-member-expr-leftmost-name left)))
- (push (list name js2-current-scope
- (setq leftpos (js2-node-abs-pos name))
- (setq end (+ leftpos (js2-node-len name))))
- js2-recorded-assignments))))
+ 'record)))))
+
+(defun js2-record-name-node (node)
+ "Saves NODE to `js2-recorded-identifiers' to check for undeclared variables
+later. NODE must be a name node."
+ (let (leftpos end)
+ (push (list node js2-current-scope
+ (setq leftpos (js2-node-abs-pos node))
+ (setq end (+ leftpos (js2-node-len node))))
+ js2-recorded-identifiers)))
(defun js2-highlight-undeclared-vars ()
- "After entire parse is finished, look for undeclared variable assignments.
+ "After entire parse is finished, look for undeclared variable references.
We have to wait until entire buffer is parsed, since JavaScript permits var
decls to occur after they're used.
If any undeclared var name is in `js2-externs' or `js2-additional-externs',
it is considered declared."
(let (name)
- (dolist (entry js2-recorded-assignments)
+ (dolist (entry js2-recorded-identifiers)
(destructuring-bind (name-node scope pos end) entry
(setq name (js2-name-node-name name-node))
(unless (or (member name js2-global-externs)
(js2-set-face pos end 'js2-external-variable-face 'record)
(js2-record-text-property pos end 'help-echo "Undeclared variable")
(js2-record-text-property pos end 'point-entered #'js2-echo-help))))
- (setq js2-recorded-assignments nil)))
+ (setq js2-recorded-identifiers nil)))
;;; IMenu support
;; possibly other browsing mechanisms.
;; The basic strategy is to identify function assignment targets of the form
-;; `foo.bar.baz', convert them to (list foo bar baz <position>), and push the
+;; `foo.bar.baz', convert them to (list fn foo bar baz <position>), and push the
;; list into `js2-imenu-recorder'. The lists are merged into a trie-like tree
;; for imenu after parsing is finished.
;; During parsing we accumulate an entry for each definition in
;; the variable `js2-imenu-recorder', like so:
-;; '((a 5)
-;; (b 25)
-;; (foo 100)
-;; (foo bar 200)
-;; (foo bar baz 300)
-;; (foo bar zab 400))
+;; '((fn a 5)
+;; (fn b 25)
+;; (fn foo 100)
+;; (fn foo bar 200)
+;; (fn foo bar baz 300)
+;; (fn foo bar zab 400))
+;; Where 'fn' is the respective function node.
;; After parsing these entries are merged into this alist-trie:
;; '((a . 1)
"this")))
(defsubst js2-node-qname-component (node)
- "Test function: return the name of this node, if it contributes to a qname.
+ "Return the name of this node, if it contributes to a qname.
Returns nil if the node doesn't contribute."
(copy-sequence
(or (js2-prop-node-name node)
(js2-function-node-name node))
(js2-name-node-name (js2-function-node-name node))))))
-(defsubst js2-record-function-qname (fn-node qname)
- "Associate FN-NODE with its QNAME for later lookup.
-This is used in postprocessing the chain list. When we find a chain
-whose first element is a js2-THIS keyword node, we look up the parent
-function and see (using this map) whether it is the tail of a chain.
-If so, we replace the this-node with a copy of the parent's qname."
+(defsubst js2-record-imenu-entry (fn-node qname pos)
+ "Add an entry to `js2-imenu-recorder'.
+FN-NODE should be the current item's function node.
+
+Associate FN-NODE with its QNAME for later lookup.
+This is used in postprocessing the chain list. For each chain, we find
+the parent function, look up its qname, then prepend a copy of it to the chain."
+ (push (cons fn-node (append qname (list pos))) js2-imenu-recorder)
(unless js2-imenu-function-map
(setq js2-imenu-function-map (make-hash-table :test 'eq)))
(puthash fn-node qname js2-imenu-function-map))
((and fun-p
(not var)
(setq fname-node (js2-function-node-name node)))
- (push (setq qname (list fname-node (js2-node-pos node)))
- js2-imenu-recorder)
- (js2-record-function-qname node qname))
+ (js2-record-imenu-entry node (list fname-node) (js2-node-pos node)))
;; for remaining forms, compute left-side tree branch first
((and var (setq qname (js2-compute-nested-prop-get var)))
(cond
;; foo.bar.baz = function
(fun-p
- (push (nconc qname (list (js2-node-pos node)))
- js2-imenu-recorder)
- (js2-record-function-qname node qname))
+ (js2-record-imenu-entry node qname (js2-node-pos node)))
;; foo.bar.baz = object-literal
;; look for nested functions: {a: {b: function() {...} }}
((js2-object-node-p node)
- (js2-record-object-literal node qname))))))))
+ ;; Node position here is still absolute, since the parser
+ ;; passes the assignment target and value expressions
+ ;; to us before they are added as children of the assignment node.
+ (js2-record-object-literal node qname (js2-node-pos node)))))))))
(defun js2-compute-nested-prop-get (node)
- "If NODE is of form foo.bar.baz, return component nodes as a list.
-Otherwise returns nil. Element-gets can be treated as property-gets
-if the index expression is a name, a string, or a positive integer."
+ "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)
(cond
((or (js2-name-node-p node)
(js2-this-node-p node))
(list node))
;; foo.bar.baz is parenthesized as (foo.bar).baz => right operand is a leaf
- ((js2-prop-get-node-p node) ; includes elem-get nodes
+ ((js2-prop-get-node-p node) ; foo.bar
(setq left (js2-prop-get-node-left node)
right (js2-prop-get-node-right node))
- (if (and (or (js2-prop-get-node-p left) ; left == foo.bar
- (js2-name-node-p left)
- (js2-this-node-p left)) ; or left == foo
- (or (js2-name-node-p right) ; .bar
- (js2-string-node-p right) ; ['bar']
- (and (js2-number-node-p right) ; [10]
- (string-match "^[0-9]+$"
- (js2-number-node-value right)))))
+ (if (setq head (js2-compute-nested-prop-get left))
+ (nconc head (list right))))
+ ((js2-elem-get-node-p node) ; foo['bar'] or foo[101]
+ (setq left (js2-elem-get-node-target node)
+ right (js2-elem-get-node-element node))
+ (if (or (js2-string-node-p right) ; ['bar']
+ (and (js2-number-node-p right) ; [10]
+ (string-match "^[0-9]+$"
+ (js2-number-node-value right))))
(if (setq head (js2-compute-nested-prop-get left))
(nconc head (list right))))))))
-(defun js2-record-object-literal (node qname)
+(defun js2-record-object-literal (node qname pos)
"Recursively process an object literal looking for functions.
NODE is an object literal that is the right-hand child of an assignment
expression. QNAME is a list of nodes representing the assignment target,
e.g. for foo.bar.baz = {...}, QNAME is (foo-node bar-node baz-node).
-We do a depth-first traversal of NODE. Any functions we find are prefixed
-with QNAME plus the property name of the function and appended to the
-variable `js2-imenu-recorder'."
- ;; Elements are relative to parent position, which is still absolute,
- ;; since the parser passes the assignment target and value expressions
- ;; to us before they are added as children of the assignment node.
- (let ((pos (js2-node-pos node))
- left right)
+POS is the absolute position of the node.
+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 (left right prop-qname)
(dolist (e (js2-object-node-elems node)) ; e is a `js2-object-prop-node'
- (setq left (js2-infix-node-left 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.
- (push (append qname (list left) (list (+ pos (js2-node-pos e))))
- js2-imenu-recorder)
- (js2-record-function-qname right qname)))
- ;; foo: {object-literal} -- add foo to qname and recurse
- ((js2-object-node-p right)
- (js2-record-object-literal right
- (append qname (list (js2-infix-node-left 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)))))))))
(defsubst js2-node-top-level-decl-p (node)
"Return t if NODE's name is defined in the top-level scope.
(js2-ast-root-p defining-scope))
(t t))))
-(defun js2-browse-postprocess-chains (chains)
+(defsubst js2-wrapper-function-p (node)
+ "Returns t if NODE is a function expression that's immediately invoked.
+NODE must be `js2-function-node'."
+ (let ((parent (js2-node-parent node)))
+ (or
+ ;; function(){...}();
+ (js2-call-node-p parent)
+ (and (js2-paren-node-p parent)
+ ;; (function(){...})();
+ (or (js2-call-node-p (setq parent (js2-node-parent parent)))
+ ;; (function(){...}).call(this);
+ (and (js2-prop-get-node-p parent)
+ (member (js2-name-node-name (js2-prop-get-node-right parent))
+ '("call" "apply"))
+ (js2-call-node-p (js2-node-parent parent))))))))
+
+(defun js2-browse-postprocess-chains (entries)
"Modify function-declaration name chains after parsing finishes.
Some of the information is only available after the parse tree is complete.
-For instance, following a 'this' reference requires a parent function node."
- (let (result head fn parent-chain p elem)
- (dolist (chain chains)
- ;; examine the head of each node to get its defining scope
- (setq head (car chain))
- (cond
- ;; if top-level/external, keep as-is
- ((js2-node-top-level-decl-p head)
- (push chain result))
- ;; check for a this-reference
- ((eq (js2-node-type head) js2-THIS)
- (setq fn (js2-node-parent-script-or-fn head))
- ;; if there is no parent function, or if the parent function
- ;; is nested, discard the head node and keep the rest of the chain.
- (if (or (null fn) (js2-nested-function-p fn))
- (push (cdr chain) result)
- ;; else look up parent in function-map. If not found, discard chain.
- (when (setq parent-chain (and js2-imenu-function-map
- (gethash fn js2-imenu-function-map)))
- ;; else discard head node and prefix parent fn qname, which is
- ;; the parent-chain sans tail, to this chain.
- (push (append (butlast parent-chain) (cdr chain)) result))))))
+For instance, processing a nested scope requires a parent function node."
+ (let (result head fn current-fn parent-qname qname p elem)
+ (dolist (entry entries)
+ ;; function node goes first
+ (destructuring-bind (current-fn &rest chain) entry
+ ;; examine its defining scope;
+ ;; if top-level/external, keep as-is
+ (if (js2-node-top-level-decl-p (car chain))
+ (push chain result)
+ (when (setq fn (js2-node-parent-script-or-fn current-fn))
+ (setq parent-qname (gethash fn js2-imenu-function-map 'not-found))
+ (when (eq parent-qname 'not-found)
+ ;; anonymous function expressions are not recorded
+ ;; during the parse, so we need to handle this case here
+ (setq parent-qname
+ (if (js2-wrapper-function-p fn)
+ (let ((grandparent (js2-node-parent-script-or-fn fn)))
+ (if (js2-ast-root-p grandparent)
+ nil
+ (gethash grandparent js2-imenu-function-map 'skip)))
+ 'skip))
+ (puthash fn parent-qname js2-imenu-function-map))
+ (unless (eq parent-qname 'skip)
+ ;; prefix parent fn qname to this chain.
+ (let ((qname (append parent-qname chain)))
+ (puthash current-fn (butlast qname) js2-imenu-function-map)
+ (push qname result)))))))
;; finally replace each node in each chain with its name.
(dolist (chain result)
(setq p chain)
'font-lock-comment-face))
(when (memq js2-ts-comment-type '(html preprocessor))
;; Tell cc-engine the bounds of the comment.
- (put-text-property js2-token-beg (1- js2-token-end) 'c-in-sws t))))
+ (js2-record-text-property js2-token-beg (1- js2-token-end) 'c-in-sws t))))
;; This function is called depressingly often, so it should be fast.
;; Most of the time it's looking at the same token it peeked before.
js2-current-flagged-token js2-EOF
js2-nesting-of-function 0
js2-labeled-stmt nil
- js2-recorded-assignments nil) ; for js2-highlight
+ js2-recorded-identifiers nil) ; for js2-highlight
(while (/= (setq tt (js2-peek-token)) js2-EOF)
(if (= tt js2-FUNCTION)
(progn
(js2-consume-token)
(setq n (js2-parse-function (if js2-called-by-compile-function
'FUNCTION_EXPRESSION
- 'FUNCTION_STATEMENT)))
- (js2-record-imenu-functions n))
+ 'FUNCTION_STATEMENT))))
;; not a function - parse a statement
(setq n (js2-parse-statement)))
;; add function or statement to script
(js2-node-add-children root comment)))
(setf (js2-node-len root) (- end pos))
;; Give extensions a chance to muck with things before highlighting starts.
- (dolist (callback js2-post-parse-callbacks)
- (funcall callback))
- (js2-highlight-undeclared-vars)
+ (let ((js2-additional-externs js2-additional-externs))
+ (dolist (callback js2-post-parse-callbacks)
+ (funcall callback))
+ (js2-highlight-undeclared-vars))
root))
(defun js2-function-parser ()
(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'."
+ (cond
+ ((js2-name-node-p node)
+ (let (leftpos)
+ (js2-define-symbol decl-type (js2-name-node-name node)
+ node ignore-not-in-block)
+ (when face
+ (js2-set-face (setq leftpos (js2-node-abs-pos node))
+ (+ leftpos (js2-node-len node))
+ face 'record))))
+ ((js2-object-node-p node)
+ (dolist (elem (js2-object-node-elems node))
+ (js2-define-destruct-symbols
+ (if (js2-object-prop-node-p elem)
+ (js2-object-prop-node-right elem)
+ ;; abbreviated destructuring {a, b}
+ elem)
+ decl-type face ignore-not-in-block)))
+ ((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))))
+ (t (js2-report-error "msg.no.parm" nil (js2-node-abs-pos node)
+ (js2-node-len node)))))
+
(defun js2-parse-function-params (fn-node pos)
(if (js2-match-token js2-RP)
(setf (js2-function-node-rp fn-node) (- js2-token-beg pos))
(cond
;; destructuring param
((or (= tt js2-LB) (= tt js2-LC))
- (push (js2-parse-primary-expr-lhs) params))
+ (setq param (js2-parse-primary-expr-lhs))
+ (js2-define-destruct-symbols param
+ js2-LP
+ 'js2-function-param-face)
+ (push param params))
;; simple name
(t
(js2-must-match js2-NAME "msg.no.parm")
(js2-name-node-name name)
fn-node))
(if (and name
- (eq function-type 'FUNCTION_EXPRESSION_STATEMENT))
+ (not (eq function-type 'FUNCTION_EXPRESSION)))
(js2-record-imenu-functions fn-node)))
(setf (js2-node-len fn-node) (- js2-ts-cursor pos)
(js2-function-node-member-expr fn-node) member-expr-node) ; may be nil
(if (js2-must-match js2-LP "msg.no.paren.for")
(setq lp (- js2-token-beg for-pos)))
(setq tt (js2-peek-token))
- ;; parse init clause
- (let ((js2-in-for-init t)) ; set as dynamic variable
- (cond
- ((= tt js2-SEMI)
- (setq init (make-js2-empty-expr-node)))
- ((or (= tt js2-VAR) (= tt js2-LET))
- (js2-consume-token)
- (setq init (js2-parse-variables tt js2-token-beg)))
- (t
- (setq init (js2-parse-expr)))))
- (if (js2-match-token js2-IN)
- (setq is-for-in t
- in-pos (- js2-token-beg for-pos)
- cond (js2-parse-expr)) ; object over which we're iterating
- ;; else ordinary for loop - parse cond and incr
- (js2-must-match js2-SEMI "msg.no.semi.for")
- (setq cond (if (= (js2-peek-token) js2-SEMI)
- (make-js2-empty-expr-node) ; no loop condition
- (js2-parse-expr)))
- (js2-must-match js2-SEMI "msg.no.semi.for.cond")
- (setq tmp-pos js2-token-end
- incr (if (= (js2-peek-token) js2-RP)
- (make-js2-empty-expr-node :pos tmp-pos)
- (js2-parse-expr))))
- (if (js2-must-match js2-RP "msg.no.paren.for.ctrl")
- (setq rp (- js2-token-beg for-pos)))
- (if (not is-for-in)
- (setq pn (make-js2-for-node :init init
- :condition cond
- :update incr
- :lp lp
- :rp rp))
- ;; cond could be null if 'in obj' got eaten by the init node.
- (if (js2-infix-node-p init)
- ;; it was (foo in bar) instead of (var foo in bar)
- (setq cond (js2-infix-node-right init)
- init (js2-infix-node-left init))
- (if (and (js2-var-decl-node-p init)
- (> (length (js2-var-decl-node-kids init)) 1))
- (js2-report-error "msg.mult.index")))
- (setq pn (make-js2-for-in-node :iterator init
- :object cond
- :in-pos in-pos
- :foreach-p is-for-each
- :each-pos each-pos
- :lp lp
- :rp rp)))
+ ;; 'for' makes local scope
+ (js2-push-scope (make-js2-scope))
(unwind-protect
- (progn
- (js2-enter-loop pn)
- ;; We have to parse the body -after- creating the loop node,
- ;; so that the loop node appears in the js2-loop-set, allowing
- ;; break/continue statements to find the enclosing loop.
- (setf body (js2-parse-statement)
- (js2-loop-node-body pn) body
- (js2-node-pos pn) for-pos
- (js2-node-len pn) (- (js2-node-end body) for-pos))
- (js2-node-add-children pn init cond incr body))
- ;; finally
- (js2-exit-loop))
+ ;; parse init clause
+ (let ((js2-in-for-init t)) ; set as dynamic variable
+ (cond
+ ((= tt js2-SEMI)
+ (setq init (make-js2-empty-expr-node)))
+ ((or (= tt js2-VAR) (= tt js2-LET))
+ (js2-consume-token)
+ (setq init (js2-parse-variables tt js2-token-beg)))
+ (t
+ (setq init (js2-parse-expr)))))
+ (if (js2-match-token js2-IN)
+ (setq is-for-in t
+ in-pos (- js2-token-beg for-pos)
+ ;; scope of iteration target object is not the scope we've created above.
+ ;; stash current scope temporary.
+ cond (let ((js2-current-scope (js2-scope-parent-scope js2-current-scope)))
+ (js2-parse-expr))) ; object over which we're iterating
+ ;; else ordinary for loop - parse cond and incr
+ (js2-must-match js2-SEMI "msg.no.semi.for")
+ (setq cond (if (= (js2-peek-token) js2-SEMI)
+ (make-js2-empty-expr-node) ; no loop condition
+ (js2-parse-expr)))
+ (js2-must-match js2-SEMI "msg.no.semi.for.cond")
+ (setq tmp-pos js2-token-end
+ incr (if (= (js2-peek-token) js2-RP)
+ (make-js2-empty-expr-node :pos tmp-pos)
+ (js2-parse-expr))))
+ (if (js2-must-match js2-RP "msg.no.paren.for.ctrl")
+ (setq rp (- js2-token-beg for-pos)))
+ (if (not is-for-in)
+ (setq pn (make-js2-for-node :init init
+ :condition cond
+ :update incr
+ :lp lp
+ :rp rp))
+ ;; cond could be null if 'in obj' got eaten by the init node.
+ (if (js2-infix-node-p init)
+ ;; it was (foo in bar) instead of (var foo in bar)
+ (setq cond (js2-infix-node-right init)
+ init (js2-infix-node-left init))
+ (if (and (js2-var-decl-node-p init)
+ (> (length (js2-var-decl-node-kids init)) 1))
+ (js2-report-error "msg.mult.index")))
+ (setq pn (make-js2-for-in-node :iterator init
+ :object cond
+ :in-pos in-pos
+ :foreach-p is-for-each
+ :each-pos each-pos
+ :lp lp
+ :rp rp)))
+ (unwind-protect
+ (progn
+ (js2-enter-loop pn)
+ ;; We have to parse the body -after- creating the loop node,
+ ;; so that the loop node appears in the js2-loop-set, allowing
+ ;; break/continue statements to find the enclosing loop.
+ (setf body (js2-parse-statement)
+ (js2-loop-node-body pn) body
+ (js2-node-pos pn) for-pos
+ (js2-node-len pn) (- (js2-node-end body) for-pos))
+ (js2-node-add-children pn init cond incr body))
+ ;; finally
+ (js2-exit-loop))
+ (js2-pop-scope))
pn))
(defun js2-parse-try ()
finally-block
saw-default-catch
peek
- var-name
+ param
catch-cond
catch-node
guard-kwd
(js2-report-error "msg.catch.unreachable"))
(if (js2-must-match js2-LP "msg.no.paren.catch")
(setq lp (- js2-token-beg catch-pos)))
- (js2-must-match js2-NAME "msg.bad.catchcond")
- (setq var-name (js2-create-name-node))
+ (js2-push-scope (make-js2-scope))
+ (let ((tt (js2-peek-token)))
+ (cond
+ ;; destructuring pattern
+ ;; catch ({ message, file }) { ... }
+ ((or (= tt js2-LB) (= tt js2-LC))
+ (setq param
+ (js2-define-destruct-symbols (js2-parse-primary-expr-lhs)
+ js2-LET nil)))
+ ;; simple name
+ (t
+ (js2-must-match js2-NAME "msg.bad.catchcond")
+ (setq param (js2-create-name-node))
+ (js2-define-symbol js2-LET js2-ts-string param))))
+ ;; pattern guard
(if (js2-match-token js2-IF)
(setq guard-kwd (- js2-token-beg catch-pos)
catch-cond (js2-parse-expr))
(setq block (js2-parse-statements)
try-end (js2-node-end block)
catch-node (make-js2-catch-node :pos catch-pos
- :var-name var-name
+ :param param
:guard-expr catch-cond
:guard-kwd guard-kwd
:block block
:lp lp
:rp rp))
+ (js2-pop-scope)
(if (js2-must-match js2-RC "msg.no.brace.after.body")
(setq try-end js2-token-beg))
(setf (js2-node-len block) (- try-end (js2-node-pos block))
(js2-node-len catch-node) (- try-end catch-pos))
- (js2-node-add-children catch-node var-name catch-cond block)
+ (js2-node-add-children catch-node param catch-cond block)
(push catch-node catch-blocks)))
((/= peek js2-FINALLY)
(js2-must-match js2-FINALLY "msg.try.no.catchfinally"
(progn
(if (and (null init) (not js2-in-for-init))
(js2-report-error "msg.destruct.assign.no.init"))
+ (js2-define-destruct-symbols destructuring
+ decl-type
+ 'font-lock-variable-name-face)
(setf (js2-var-init-node-target vi) destructuring))
(setf (js2-var-init-node-target vi) name))
(setf (js2-var-init-node-initializer vi) init)
(js2-pop-scope))
pn))
-(defsubst js2-define-new-symbol (decl-type name node)
- (js2-scope-put-symbol js2-current-scope
+(defsubst js2-define-new-symbol (decl-type name node &optional scope)
+ (js2-scope-put-symbol (or scope js2-current-scope)
name
(make-js2-symbol decl-type name node)))
(js2-add-strict-warning "msg.var.redecl" name)
(if (and js2-strict-var-hides-function-arg-warning (= sdt js2-LP))
(js2-add-strict-warning "msg.var.hides.arg" name)))
- (js2-define-new-symbol decl-type name node)))
+ (js2-define-new-symbol decl-type name node
+ js2-current-script-or-fn)))
((= decl-type js2-LP)
(if symbol
;; must be duplicate parameter. Second parameter hides the
:value js2-ts-string
:flags flags)
(js2-set-face px-pos js2-ts-cursor 'font-lock-string-face 'record)
- (put-text-property px-pos js2-ts-cursor 'syntax-table '(2))))
+ (js2-record-text-property px-pos js2-ts-cursor 'syntax-table '(2))))
((or (= tt js2-NULL)
(= tt js2-THIS)
(= tt js2-FALSE)
(defun js2-parse-name (tt-flagged tt)
(let ((name js2-ts-string)
- (name-pos js2-token-beg))
- (if (and (js2-flag-set-p tt-flagged js2-ti-check-label)
- (= (js2-peek-token) js2-COLON))
- (prog1
+ (name-pos js2-token-beg)
+ node)
+ (if (and (js2-flag-set-p tt-flagged js2-ti-check-label)
+ (= (js2-peek-token) js2-COLON))
+ (prog1
;; Do not consume colon, it is used as unwind indicator
;; to return to statementHelper.
(make-js2-label-node :pos name-pos
:len (- js2-token-end name-pos)
:name name)
- (js2-set-face name-pos
- js2-token-end
- 'font-lock-variable-name-face 'record))
- ;; Otherwise not a label, just a name. Unfortunately peeking
- ;; the next token to check for a colon has biffed js2-token-beg
- ;; and js2-token-end. We store the name's bounds in buffer vars
- ;; and `js2-create-name-node' uses them.
- (js2-save-name-token-data name-pos name)
- (if js2-compiler-xml-available
- (js2-parse-property-name nil name 0)
- (js2-create-name-node 'check-activation)))))
+ (js2-set-face name-pos
+ js2-token-end
+ 'font-lock-variable-name-face 'record))
+ ;; Otherwise not a label, just a name. Unfortunately peeking
+ ;; the next token to check for a colon has biffed js2-token-beg
+ ;; and js2-token-end. We store the name's bounds in buffer vars
+ ;; and `js2-create-name-node' uses them.
+ (js2-save-name-token-data name-pos name)
+ (setq node (if js2-compiler-xml-available
+ (js2-parse-property-name nil name 0)
+ (js2-create-name-node 'check-activation)))
+ (if js2-highlight-external-variables
+ (js2-record-name-node node))
+ node)))
(defsubst js2-parse-warn-trailing-comma (msg pos elems comma-pos)
(js2-add-strict-warning
elems
pn
(continue t))
+ (unless js2-is-in-lhs
+ (js2-push-scope (make-js2-scope))) ; for array comp
(while continue
(setq tt (js2-peek-token))
(cond
:len (- js2-ts-cursor pos)
:elems (nreverse elems)))
(apply #'js2-node-add-children pn (js2-array-node-elems pn))
- (when after-comma
+ (when (and after-comma (not js2-is-in-lhs))
(js2-parse-warn-trailing-comma "msg.array.trailing.comma"
pos elems after-comma)))
;; destructuring binding
(push (js2-parse-assign-expr) elems)
(setq after-lb-or-comma nil
after-comma nil))))
+ (unless js2-is-in-lhs
+ (js2-pop-scope))
pn))
(defun js2-parse-array-comprehension (expr pos)
POS is the beginning of the LB token preceding EXPR.
We should have just parsed the 'for' keyword before calling this function."
(let (loops
+ loop
+ first
+ prev
filter
if-pos
result)
(while (= (js2-peek-token) js2-FOR)
- (push (js2-parse-array-comp-loop) loops))
+ (let ((prev (car loops))) ; rearrange scope chain
+ (push (setq loop (js2-parse-array-comp-loop)) loops)
+ (if prev ; each loop is parent scope to the next one
+ (setf (js2-scope-parent-scope loop) prev)
+ ; first loop takes expr scope's parent
+ (setf (js2-scope-parent-scope (setq first loop))
+ (js2-scope-parent-scope js2-current-scope)))))
+ ;; set expr scope's parent to the last loop
+ (setf (js2-scope-parent-scope js2-current-scope) (car loops))
(when (= (js2-peek-token) js2-IF)
(js2-consume-token)
(setq if-pos (- js2-token-beg pos) ; relative
:if-pos if-pos))
(apply #'js2-node-add-children result expr (car filter)
(js2-array-comp-node-loops result))
+ (setq js2-current-scope first) ; pop to the first loop
result))
(defun js2-parse-array-comp-loop ()
((or (= tt js2-LB)
(= tt js2-LC))
;; handle destructuring assignment
- (setq iter (js2-parse-primary-expr-lhs)))
+ (setq iter (js2-parse-primary-expr-lhs))
+ (js2-define-destruct-symbols iter js2-LET
+ 'font-lock-variable-name-face t))
((js2-valid-prop-name-token tt)
(js2-consume-token)
(setq iter (js2-create-name-node)))
(= (js2-peek-token) js2-NAME)
(or (string= prop "get")
(string= prop "set")))
- (progn
- (js2-consume-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 (string= prop "get"))))
+ (js2-consume-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 (string= prop "get")))
;; abbreviated destructuring bind e.g., {a, b} = c;
;; XXX: To be honest, the value of `js2-is-in-lhs' becomes t only when
;; patterns are appeared in variable declaration, function parameters, and catch-clause.
(or (= ctk js2-COMMA)
(= ctk js2-RC)
(js2-valid-prop-name-token ctk))))
- (js2-set-face ppos pend 'font-lock-variable-name-face 'record)
name)
;; regular prop
(t
;; Karl for coming up with the initial approach, which packs a lot of
;; punch for so little code.
-(defconst js-possibly-braceless-keyword-re
- (regexp-opt
- '("catch" "do" "else" "finally" "for" "if" "each" "try" "while" "with" "let")
- 'words)
+(defconst js-possibly-braceless-keywords-re
+ (concat "else[ \t]+if\\|for[ \t]+each\\|"
+ (regexp-opt '("catch" "do" "else" "finally" "for" "if"
+ "try" "while" "with" "let")))
"Regular expression matching keywords that are optionally
followed by an opening brace.")
-(defconst js-possibly-braceless-keywords-re
- "\\([ \t}]*else[ \t]+if\\|[ \t}]*for[ \t]+each\\)"
- "Regular expression which matches the keywords which are consist of more than 2 words
-like 'if else' and 'for each', and optionally followed by an opening brace.")
-
(defconst js-indent-operator-re
(concat "[-+*/%<>=&^|?:.]\\([^-+*/]\\|$\\)\\|"
(regexp-opt '("in" "instanceof") 'words))
"Regular expression matching operators that affect indentation
of continued expressions.")
+(defconst js-declaration-keyword-re
+ (regexp-opt '("var" "let" "const") 'words)
+ "Regular expression matching variable declaration keywords.")
+
;; This function has horrible results if you're typing an array
;; such as [[1, 2], [3, 4], [5, 6]]. Bounce indenting -really- sucks
;; in conjunction with electric-indent, so just disabling it.
(call-interactively cmd)))
;; don't do the electric keys inside comments or strings,
;; and don't do bounce-indent with them.
- (let ((parse-state (parse-partial-sexp (point-min) (point)))
+ (let ((parse-state (syntax-ppss (point)))
(js2-bounce-indent-p (js2-code-at-bol-p)))
(unless (or (nth 3 parse-state)
(nth 4 parse-state))
(defun js-re-search-forward-inner (regexp &optional bound count)
"Auxiliary function for `js-re-search-forward'."
- (let ((parse)
- (saved-point (point-min)))
+ (let (parse saved-point)
(while (> count 0)
(re-search-forward regexp bound)
- (setq parse (parse-partial-sexp saved-point (point)))
+ (setq parse (if saved-point
+ (parse-partial-sexp saved-point (point))
+ (syntax-ppss (point))))
(cond ((nth 3 parse)
(re-search-forward
(concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
(defun js-re-search-backward-inner (regexp &optional bound count)
"Auxiliary function for `js-re-search-backward'."
- (let ((parse)
- (saved-point (point-min)))
+ (let (parse saved-point)
(while (> count 0)
(re-search-backward regexp bound)
- (setq parse (parse-partial-sexp saved-point (point)))
+ (setq parse (if saved-point
+ (parse-partial-sexp saved-point (point))
+ (syntax-ppss (point))))
(cond ((nth 3 parse)
(re-search-backward
(concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
(and (js-re-search-backward "\n" nil t)
(progn
(skip-chars-backward " \t")
- (backward-char)
- (and (js-looking-at-operator-p)
- (and (progn (backward-char)
- (not (looking-at "\\*\\|++\\|--\\|/[/*]"))))))))))
+ (unless (bolp)
+ (backward-char)
+ (and (js-looking-at-operator-p)
+ (and (progn
+ (backward-char)
+ (not (looking-at "\\*\\|++\\|--\\|/[/*]")))))))))))
(defun js-end-of-do-while-loop-p ()
"Returns non-nil if word after point is `while' of a do-while
"\\<while\\>" (point-at-eol) t))
(= (current-indentation) saved-indent)))))))))
-(defun js-get-multiline-declaration-offset ()
- "Returns offset (> 0) if the current line is part of
- multi-line variable declaration like below example, and
- returns 0 otherwise.
+(defun js-multiline-decl-indentation ()
+ "Returns the declaration indentation column if the current line belongs
+to a multiline declaration statement. All declarations are lined up vertically:
+
+var a = 10,
+ b = 20,
+ c = 30;
- var a = 10,
- b = 20,
- c = 30;
+Note that if `js2-always-indent-assigned-expr-in-decls-p' is nil, and the first
+assigned expression is a function or array/object literal, it will be indented
+differently:
+var o = { var bar = 2,
+ foo: 3 o = {
+}, foo: 3
+ bar = 2; };
"
- (let* ((node (js2-node-at-point))
- (pnode (and node (js2-node-parent node)))
- (pnode-type (and pnode (js2-node-type pnode))))
- (if (and node
- (= js2-NAME (js2-node-type node))
- (or
- (= js2-VAR pnode-type)
- (= js2-LET pnode-type)
- (= js2-CONST pnode-type)))
- (if (= js2-CONST pnode-type)
- 6
- 4)
- 0)))
+ (let (forward-sexp-function ; use lisp version
+ at-opening-bracket)
+ (save-excursion
+ (back-to-indentation)
+ (when (not (looking-at js-declaration-keyword-re))
+ (when (looking-at js-indent-operator-re)
+ (goto-char (match-end 0))) ; continued expressions are ok
+ (while (and (not at-opening-bracket)
+ (not (bobp))
+ (let ((pos (point)))
+ (save-excursion
+ (js2-backward-sws)
+ (or (eq (char-before) ?,)
+ (and (not (eq (char-before) ?\;))
+ (and
+ (prog2 (skip-chars-backward "[[:punct:]]")
+ (looking-at js-indent-operator-re)
+ (js2-backward-sws))
+ (not (eq (char-before) ?\;))))
+ (js2-same-line pos)))))
+ (condition-case err
+ (backward-sexp)
+ (scan-error (setq at-opening-bracket t))))
+ (when (looking-at js-declaration-keyword-re)
+ (- (1+ (match-end 0)) (point-at-bol)))))))
(defun js-ctrl-statement-indentation ()
"Returns the proper indentation of the current line if it
(let (forward-sexp-function) ; temporarily unbind it
(save-excursion
(back-to-indentation)
- (when (save-excursion
- (and (not (js2-same-line (point-min)))
- (not (looking-at "{"))
- (js-re-search-backward "[[:graph:]]" nil t)
- (not (looking-at "[{([]"))
- (progn
- (forward-char)
+ (when (and (not (js2-same-line (point-min)))
+ (not (looking-at "{"))
+ (js-re-search-backward "[[:graph:]]" nil t)
+ (not (looking-at "[{([]"))
+ (progn
+ (forward-char)
+ (when (= (char-before) ?\))
;; scan-sexps sometimes throws an error
(ignore-errors (backward-sexp))
- (when (looking-at "(") (backward-word 1))
- (and (save-excursion
- (skip-chars-backward " \t}" (point-at-bol))
- (or (bolp)
- (and (backward-word 1)
- (skip-chars-backward " \t}" (point-at-bol))
- (bolp)
- (looking-at js-possibly-braceless-keywords-re))))
- (looking-at js-possibly-braceless-keyword-re)
+ (skip-chars-backward " \t" (point-at-bol)))
+ (let ((pt (point)))
+ (back-to-indentation)
+ (and (looking-at js-possibly-braceless-keywords-re)
+ (= (match-end 0) pt)
(not (js-end-of-do-while-loop-p))))))
- (save-excursion
- (goto-char (match-beginning 0))
- (+ (current-indentation) js2-basic-offset))))))
+ (+ (current-indentation) js2-basic-offset)))))
(defun js2-indent-in-array-comp (parse-status)
"Return non-nil if we think we're in an array comprehension.
(match-beginning 0)))
;; to skip arbitrary expressions we need the parser,
;; so we'll just guess at it.
- (if (re-search-forward "[^,]* \\(for\\) " end t)
+ (if (and (> end (point)) ; not empty literal
+ (re-search-forward "[^,]]* \\(for\\) " end t))
(match-beginning 1))))))))
(defun js2-array-comp-indentation (parse-status for-kwd)
(let ((ctrl-stmt-indent (js-ctrl-statement-indentation))
(same-indent-p (looking-at "[]})]\\|\\<case\\>\\|\\<default\\>"))
(continued-expr-p (js-continued-expression-p))
- (multiline-declaration-offset (js-get-multiline-declaration-offset))
+ (declaration-indent (and js2-pretty-multiline-decl-indentation-p
+ (js-multiline-decl-indentation)))
(bracket (nth 1 parse-status))
beg)
(cond
(ctrl-stmt-indent)
+ ((and declaration-indent continued-expr-p)
+ (+ declaration-indent js2-basic-offset))
+
+ (declaration-indent)
+
(bracket
(goto-char bracket)
(cond
(not js2-consistent-level-indent-inner-bracket-p))
(progn (goto-char (1+ (nth 1 p)))
(skip-chars-forward " \t"))
- (back-to-indentation))
+ (back-to-indentation)
+ (when (and js2-pretty-multiline-decl-indentation-p
+ js2-always-indent-assigned-expr-in-decls-p
+ (looking-at js-declaration-keyword-re))
+ (goto-char (1+ (match-end 0)))))
(cond (same-indent-p
(current-column))
(continued-expr-p
(+ (current-column) (* 2 js2-basic-offset)))
- ((> multiline-declaration-offset 0)
- (+ (current-column) js2-basic-offset multiline-declaration-offset))
(t
(+ (current-column) js2-basic-offset)))))
(t
(continued-expr-p js2-basic-offset)
- ((> multiline-declaration-offset 0)
- (+ multiline-declaration-offset))
-
(t 0)))))
(defun js2-lineup-comment (parse-status)
(skip-chars-forward " \t")
(point)))
-(defun js2-bounce-indent (normal-col parse-status)
+(defun js2-bounce-indent (normal-col parse-status backwards)
"Cycle among alternate computed indentation positions.
PARSE-STATUS is the result of `parse-partial-sexp' from the beginning
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."
+bracket, brace and statement nesting. If BACKWARDS, cycle positions
+in reverse."
(let ((cur-indent (js2-current-indent))
(old-buffer-undo-list buffer-undo-list)
;; Emacs 21 only has `count-lines', not `line-number-at-pos'
(1+ (count-lines (point-min) (point)))))
positions
pos
+ main-pos
anchor
arglist-cont
same-indent
(current-column)))))
(when pos
(incf pos js2-basic-offset)
- (unless (member pos positions)
- (push pos positions)))
+ (push pos positions))
;; third likely point: same indent as previous line of code.
;; Make it the first likely point if we're not on an
(js2-indent-in-objlit-p parse-status))
(not (js2-arglist-close)))
(setq same-indent pos))
- (unless (member pos positions)
- (push pos positions)))
+ (push pos positions))
;; fourth likely point: first preceding code with less indentation
;; than the immediately preceding code line.
(setq pos (save-excursion
+ (back-to-indentation)
(js2-backward-sws)
(back-to-indentation)
(setq anchor (current-column))
(current-column))
anchor)))
(setq pos (current-column))))
- (unless (member pos positions)
- (push pos positions))
+ (push pos positions)
- ;; put nesting-heuristic position first in list, sort rest
- (setq positions (nreverse (sort positions '<)))
- (setq positions (cons normal-col (delete normal-col positions)))
+ ;; nesting-heuristic position, main by default
+ (push (setq main-pos normal-col) positions)
+
+ ;; delete duplicates and sort positions list
+ (setq positions (sort (delete-dups positions) '<))
;; comma-list continuation lines: prev line indent takes precedence
(if same-indent
- (setq positions
- (cons same-indent
- (sort (delete same-indent positions) '<))))
+ (setq main-pos same-indent))
;; common special cases where we want to indent in from previous line
(if (or (js2-indent-case-block-p)
(js2-indent-objlit-arg-p parse-status))
- (setq positions
- (cons basic-offset
- (delete basic-offset positions))))
+ (setq main-pos basic-offset))
+
+ ;; if bouncing backwards, reverse positions list
+ (if backwards
+ (setq positions (reverse positions)))
;; record whether we're already sitting on one of the alternatives
(setq pos (member cur-indent positions))
+
(cond
;; case 0: we're one one of the alternatives and this is the
;; first time they've pressed TAB on this line (best-guess).
(setq computed-pos 0))
;; case 2: not on any of the computed spots => use main spot
((not pos)
- (setq computed-pos 0))
+ (setq computed-pos (js2-position main-pos positions)))
;; case 3: on last position: cycle to first position
((null (cdr pos))
(setq computed-pos 0))
;; see commentary for `js2-mode-last-indented-line'
(setq js2-mode-last-indented-line current-line))))
+(defun js2-indent-bounce-backwards ()
+ "Calls `js2-indent-line'. When `js2-bounce-indent-p',
+cycles between the computed indentation positions in reverse order."
+ (interactive)
+ (js2-indent-line t))
+
(defsubst js2-1-line-comment-continuation-p ()
"Return t if we're in a 1-line comment continuation.
If so, we don't ever want to use bounce-indent."
(forward-line 0))
(looking-at "\\s-*//"))))))
-(defun js2-indent-line ()
+(defun js2-indent-line (&optional bounce-backwards)
"Indent the current line as JavaScript source text."
(interactive)
(let (parse-status
- current-indent
offset
indent-col
moved
;; This has to be set before calling parse-partial-sexp below.
(inhibit-point-motion-hooks t))
(setq parse-status (save-excursion
- (parse-partial-sexp (point-min)
- (point-at-bol)))
+ (syntax-ppss (point-at-bol)))
offset (- (point) (save-excursion
(back-to-indentation)
- (setq current-indent (current-column))
(point))))
(js2-with-underscore-as-word-syntax
(if (nth 4 parse-status)
(js2-lineup-comment parse-status)
(setq indent-col (js-proper-indentation parse-status))
;; see comments below about js2-mode-last-indented-line
- (when
- (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)
- (setq moved t))
- ;; just indent to the guesser's likely spot
- ((/= current-indent indent-col)
- (indent-line-to indent-col)
- (setq moved t)))
- (when (and moved (plusp offset))
- (forward-char offset)))))))
+ (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 (plusp offset)
+ (forward-char offset))))))
(defun js2-indent-region (start end)
"Indent the region, but don't use bounce indenting."
(let ((js2-bounce-indent-p nil)
- (indent-region-function nil))
- (indent-region start end nil))) ; nil for byte-compiler
+ (indent-region-function nil)
+ (after-change-functions (remq 'js2-mode-edit
+ after-change-functions)))
+ (indent-region start end nil) ; nil for byte-compiler
+ (js2-mode-edit start end (- end start))))
;;;###autoload (add-to-list 'auto-mode-alist '("\\.js$" . js2-mode))
(kill-all-local-variables)
(set-syntax-table js2-mode-syntax-table)
(use-local-map js2-mode-map)
+ (make-local-variable 'comment-start)
+ (make-local-variable 'comment-end)
+ (make-local-variable 'comment-start-skip)
(setq major-mode 'js2-mode
mode-name "JavaScript-IDE"
comment-start "//" ; used by comment-region; don't change it
(add-to-invisibility-spec '(js2-outline . t))
(set (make-local-variable 'line-move-ignore-invisible) t)
(set (make-local-variable 'forward-sexp-function) #'js2-mode-forward-sexp)
+
+ (if (fboundp 'run-mode-hooks)
+ (run-mode-hooks 'js2-mode-hook)
+ (run-hooks 'js2-mode-hook))
+
(setq js2-mode-functions-hidden nil
js2-mode-comments-hidden nil
js2-mode-buffer-dirty-p t
js2-mode-parsing nil)
- (js2-reparse)
- (run-hooks 'js2-mode-hook))
+ (js2-reparse))
(defun js2-mode-exit ()
"Exit `js2-mode' and clean up."
(cancel-timer js2-mode-parse-timer))
(setq js2-mode-parsing nil)
(setq js2-mode-parse-timer
- (run-with-idle-timer js2-idle-timer-delay nil #'js2-reparse)))
+ (run-with-idle-timer js2-idle-timer-delay nil
+ #'js2-mode-idle-reparse (current-buffer))))
+
+(defun js2-mode-idle-reparse (buffer)
+ "Run `js2-reparse' if BUFFER is the current buffer, or schedule
+it to be reparsed when the buffer is selected."
+ (if (eq buffer (current-buffer))
+ (js2-reparse)
+ ;; reparse when the buffer is selected again
+ (with-current-buffer buffer
+ (add-hook 'window-configuration-change-hook
+ #'js2-mode-idle-reparse-inner
+ t))))
+
+(defun js2-mode-idle-reparse-inner ()
+ (remove-hook 'window-configuration-change-hook
+ #'js2-mode-idle-reparse-inner
+ t)
+ (js2-reparse))
(defun js2-mode-edit (beg end len)
"Schedule a new parse after buffer is edited.
(when (or js2-mode-buffer-dirty-p force)
(js2-remove-overlays)
(js2-with-unmodifying-text-property-changes
- (remove-text-properties (point-min) (point-max) '(syntax-table))
(setq js2-mode-buffer-dirty-p nil
js2-mode-fontifications nil
- js2-mode-deferred-properties nil
- js2-additional-externs nil)
+ js2-mode-deferred-properties nil)
(if js2-mode-verbose-parse-p
(message "parsing..."))
(setq time
(setq interrupted-p
(catch 'interrupted
(setq js2-mode-ast (js2-parse))
- (when (plusp js2-highlight-level)
- (js2-mode-fontify-regions))
+ ;; if parsing is interrupted, comments and regex
+ ;; literals stay ignored by `parse-partial-sexp'
+ (remove-text-properties (point-min) (point-max)
+ '(syntax-table))
+ (js2-mode-apply-deferred-properties)
(js2-mode-remove-suppressed-warnings)
(js2-mode-show-warnings)
(js2-mode-show-errors)
for o in (overlays-at pos)
thereis (overlay-get o 'js2-error)))
-(defun js2-mode-fontify-regions ()
- "Apply fontifications recorded during parsing."
- ;; We defer clearing faces as long as possible to eliminate flashing.
- (js2-clear-face (point-min) (point-max))
- ;; have to reverse the recorded fontifications so that errors and
- ;; warnings overwrite the normal fontifications
- (dolist (f (nreverse js2-mode-fontifications))
- (put-text-property (first f) (second f) 'face (third f)))
- (setq js2-mode-fontifications nil)
+(defun js2-mode-apply-deferred-properties ()
+ "Apply fontifications and other text properties recorded during parsing."
+ (when (plusp js2-highlight-level)
+ ;; We defer clearing faces as long as possible to eliminate flashing.
+ (js2-clear-face (point-min) (point-max))
+ ;; Have to reverse the recorded fontifications list so that errors
+ ;; and warnings overwrite the normal fontifications.
+ (dolist (f (nreverse js2-mode-fontifications))
+ (put-text-property (first f) (second f) 'face (third f)))
+ (setq js2-mode-fontifications nil))
(dolist (p js2-mode-deferred-properties)
(apply #'put-text-property p))
(setq js2-mode-deferred-properties nil))
(defun js2-echo-error (old-point new-point)
"Called by point-motion hooks."
(let ((msg (get-text-property new-point 'help-echo)))
- (if msg
+ (if (and msg (or (not (current-message))
+ (string= (current-message) "Quit")))
(message msg))))
(defalias #'js2-echo-help #'js2-echo-error)
"Handle user pressing the Enter key."
(interactive)
(let ((parse-status (save-excursion
- (parse-partial-sexp (point-min) (point)))))
+ (syntax-ppss (point))))
+ (js2-bounce-indent-p nil))
(cond
;; check if we're inside a string
((nth 3 parse-status)
(t
;; should probably figure out what the mode-map says we should do
(if js2-indent-on-enter-key
- (let ((js2-bounce-indent-p nil))
- (js2-indent-line)))
+ (js2-indent-line))
(insert "\n")
(if js2-enter-indents-newline
- (let ((js2-bounce-indent-p nil))
- (js2-indent-line)))))))
+ (js2-indent-line))))))
(defun js2-mode-split-string (parse-status)
"Turn a newline in mid-string into a string concatenation.
(backward-char 1))))
(defun js2-mode-extend-comment ()
- "When inside a comment block, add comment prefix."
+ "Indent the line and, when inside a comment block, add comment prefix."
(let (star single col first-line needs-close)
(save-excursion
(back-to-indentation)
(insert "\n")
(indent-to col)
(insert "*/"))))
- (single
- (when (save-excursion
+ ((and single
+ (save-excursion
(and (zerop (forward-line 1))
- (looking-at "\\s-*//")))
- (indent-to col)
- (insert "// "))))))
+ (looking-at "\\s-*//"))))
+ (indent-to col)
+ (insert "// "))
+ ;; don't need to extend the comment after all
+ (js2-enter-indents-newline
+ (js2-indent-line)))))
(defun js2-beginning-of-line ()
"Toggles point between bol and first non-whitespace char in line.
"Return non-nil if inside a string.
Actually returns the quote character that begins the string."
(let ((parse-state (save-excursion
- (parse-partial-sexp (point-min) (point)))))
+ (syntax-ppss (point)))))
(nth 3 parse-state)))
(defsubst js2-mode-inside-comment-or-string ()
(and comment-start
(<= comment-start (point))))
(let ((parse-state (save-excursion
- (parse-partial-sexp (point-min) (point)))))
+ (syntax-ppss (point)))))
(or (nth 3 parse-state)
(nth 4 parse-state)))))
(defun js2-mode-match-single-quote ()
"Insert matching single-quote."
(interactive)
- (let ((parse-status (parse-partial-sexp (point-min) (point))))
+ (let ((parse-status (syntax-ppss (point))))
;; don't match inside comments, since apostrophe is more common
(if (nth 4 parse-status)
(insert "'")
"Skip over close-paren rather than inserting, where appropriate."
(interactive)
(let* ((here (point))
- (parse-status (parse-partial-sexp (point-min) here))
+ (parse-status (syntax-ppss here))
(open-pos (nth 1 parse-status))
(close last-input-event)
(open (cond