pos)
(unless buf
(error "No buffer available for node %s" node))
- (save-excursion
- (set-buffer buf)
+ (with-current-buffer buf
(buffer-substring-no-properties (setq pos (js2-node-abs-pos node))
(+ pos (js2-node-len 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)
end (max (point-min) end))
(if record
(push (list beg end face) js2-mode-fontifications)
- (put-text-property beg end 'face face))))
+ (put-text-property beg end 'font-lock-face face))))
(defsubst js2-set-kid-face (pos kid len face)
"Set-face on a child node.
(js2-set-face start (+ start length) 'font-lock-keyword-face))
(defsubst js2-clear-face (beg end)
- (remove-text-properties beg end '(face nil
+ (remove-text-properties beg end '(font-lock-face nil
help-echo nil
point-entered nil
c-in-sws nil)))
;; 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)
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).
POS is the absolute position of the 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'."
+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'
(let ((left (js2-infix-node-left e))
;; 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 (setq prop-qname (append qname (list left pos)))
- js2-imenu-recorder)
- (js2-record-function-qname right prop-qname)))
+ (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
'("call" "apply"))
(js2-call-node-p (js2-node-parent parent))))))))
-(defun js2-browse-postprocess-chains (chains)
+(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 ((js2-imenu-fn-type-map (make-hash-table :test 'eq))
- result head fn fn-type parent-chain p elem parent)
- (dolist (chain chains)
- ;; examine the head of each node to get its defining scope
- (setq head (car chain))
- ;; if top-level/external, keep as-is
- (if (js2-node-top-level-decl-p head)
- (push chain result)
- (cond
- ;; starts with this-reference
- ((js2-this-node-p head)
- (setq fn (js2-node-parent-script-or-fn head)
- chain (cdr chain))) ; discard this-node
- ;; nested named function
- ((js2-function-node-p (setq parent (js2-node-parent head)))
- (setq fn (js2-node-parent-script-or-fn parent)))
- ;; variable assigned a function expression
- (t (setq fn (js2-node-parent-script-or-fn head))))
- (when fn
- (setq fn-type (gethash fn js2-imenu-fn-type-map))
- (unless fn-type
- (setq fn-type
- (cond ((js2-nested-function-p fn) 'skip)
- ((setq parent-chain
- (gethash fn js2-imenu-function-map))
- 'named)
- ((js2-wrapper-function-p fn) 'anon)
- (t 'skip)))
- (puthash fn fn-type js2-imenu-fn-type-map))
- (case fn-type
- ('anon (push chain result)) ; anonymous top-level wrapper
- ('named ; top-level named function
- ;; prefix parent fn qname, which is
- ;; parent-chain sans last elem, to this chain.
- (push (append (butlast parent-chain) 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)
;; a nested sub-alist element looks like (INDEX-NAME SUB-ALIST).
;; The sub-alist entries immediately follow INDEX-NAME, the head of the list.
-(defsubst js2-treeify (lst)
+(defun js2-treeify (lst)
"Convert (a b c d) to (a ((b ((c d)))))"
(if (null (cddr lst)) ; list length <= 2
lst
(max-specpdl-size (max max-specpdl-size 3000))
(case-fold-search nil)
ast)
- (or buf (setq buf (current-buffer)))
(message nil) ; clear any error message from previous parse
(save-excursion
- (set-buffer buf)
+ (when buf (set-buffer buf))
(setq js2-scanned-comments nil
js2-parsed-errors nil
js2-parsed-warnings nil
(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
;; We do our own syntax highlighting based on the parse tree.
;; However, we want minor modes that add keywords to highlight properly
- ;; (examples: doxymacs, column-marker). We do this by not letting
- ;; font-lock unfontify anything, and telling it to fontify after we
- ;; re-parse and re-highlight the buffer. (We currently don't do any
- ;; work with regions other than the whole buffer.)
- (dolist (var '(font-lock-unfontify-buffer-function
- font-lock-unfontify-region-function))
- (set (make-local-variable var) (lambda (&rest args) t)))
-
- ;; Don't let font-lock do syntactic (string/comment) fontification.
- (set (make-local-variable #'font-lock-syntactic-face-function)
- (lambda (state) nil))
-
+ ;; (examples: doxymacs, column-marker).
+ ;; To customize highlighted keywords, use `font-lock-add-keywords'.
+ (setq font-lock-defaults '(nil t))
+
;; Experiment: make reparse-delay longer for longer files.
(if (plusp js2-dynamic-idle-timer-adjust)
(setq js2-idle-timer-delay
(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)
-
- (if (fboundp 'run-mode-hooks)
- (run-mode-hooks 'js2-mode-hook)
- (run-hooks 'js2-mode-hook)))
+ (js2-reparse))
(defun js2-mode-exit ()
"Exit `js2-mode' and clean up."
(js2-mode-hide-overlay)
(js2-mode-reset-timer))
-(defun js2-mode-run-font-lock ()
- "Run `font-lock-fontify-buffer' after parsing/highlighting.
-This is intended to allow modes that install their own font-lock keywords
-to work with js2-mode. In practice it never seems to work for long.
-Hopefully the Emacs maintainers can help figure out a way to make it work."
- (when (and (boundp 'font-lock-keywords)
- font-lock-keywords
- (boundp 'font-lock-mode)
- font-lock-mode)
- ;; TODO: font-lock and jit-lock really really REALLY don't want to
- ;; play nicely with js2-mode. They go out of their way to fail to
- ;; provide any option for saying "look, fontify the farging buffer
- ;; with just the keywords already". Argh.
- (setq font-lock-defaults (list font-lock-keywords 'keywords-only))
- (let (font-lock-verbose)
- (font-lock-fontify-buffer))))
-
(defun js2-reparse (&optional force)
"Re-parse current buffer after user finishes some data entry.
If we get any user input while parsing, including cursor motion,
(js2-mode-remove-suppressed-warnings)
(js2-mode-show-warnings)
(js2-mode-show-errors)
- (js2-mode-run-font-lock) ; note: doesn't work
(js2-mode-highlight-magic-parens)
(if (>= js2-highlight-level 1)
(js2-highlight-jsdoc js2-mode-ast))
(if js2-mode-node-overlay
(move-overlay js2-mode-node-overlay beg end)
(setq js2-mode-node-overlay (make-overlay beg end))
- (overlay-put js2-mode-node-overlay 'face 'highlight))
+ (overlay-put js2-mode-node-overlay 'font-lock-face 'highlight))
(js2-with-unmodifying-text-property-changes
(put-text-property beg end 'point-left #'js2-mode-hide-overlay))
(message "%s, parent: %s"
(end (max (point-min) (min end (point-max))))
(js2-highlight-level 3) ; so js2-set-face is sure to fire
(ovl (make-overlay beg end)))
- (overlay-put ovl 'face face)
+ (overlay-put ovl 'font-lock-face face)
(overlay-put ovl 'js2-error t)
(put-text-property beg end 'help-echo (js2-get-msg key))
(put-text-property beg end 'point-entered #'js2-echo-error)))
;; 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)))
+ (put-text-property (first f) (second f) 'font-lock-face (third f)))
(setq js2-mode-fontifications nil))
(dolist (p js2-mode-deferred-properties)
(apply #'put-text-property p))
(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)
Sets value of `js2-magic' text property to line number at POS."
(propertize delim
'js2-magic (line-number-at-pos pos)
- 'face 'js2-magic-paren-face))
+ 'font-lock-face 'js2-magic-paren-face))
(defun js2-mode-match-delimiter (open close)
"Insert OPEN (a string) and possibly matching delimiter CLOSE.
(if (get-text-property beg 'js2-magic)
(js2-with-unmodifying-text-property-changes
(put-text-property beg (or end (1+ beg))
- 'face 'js2-magic-paren-face))))))
+ 'font-lock-face 'js2-magic-paren-face))))))
(defun js2-mode-mundanify-parens ()
"Clear all magic parens and brackets."