]> code.delx.au - gnu-emacs-elpa/blob - js2-imenu-extras.el
32bfce31cafc21e8a492e94c4d1676d885d84bc3
[gnu-emacs-elpa] / js2-imenu-extras.el
1 (eval-when-compile
2 (require 'cl))
3
4 (require 'js2-mode)
5
6 (defconst js2-imenu-extension-styles
7 `((:framework jquery
8 :call-re "\\_<\\(?:jQuery\\|\\$\\|_\\)\\.extend\\s-*("
9 :recorder js2-imenu-record-jquery-extend)
10
11 (:framework jquery-ui
12 :call-re "^\\s-*\\(?:jQuery\\|\\$\\)\\.widget\\s-*("
13 :recorder js2-imenu-record-string-declare)
14
15 (:framework dojo
16 :call-re "^\\s-*dojo.declare\\s-*("
17 :recorder js2-imenu-record-string-declare)
18
19 (:framework backbone
20 :call-re ,(concat "\\_<" js2-mode-identifier-re "\\.extend\\s-*(")
21 :recorder js2-imenu-record-backbone-extend)))
22
23 (defconst js2-imenu-available-frameworks
24 (mapcar (lambda (style) (plist-get style :framework)) js2-imenu-extension-styles)
25 "List of available JavaScript framework symbols.")
26
27 (defcustom js2-imenu-enabled-frameworks js2-imenu-available-frameworks
28 "Frameworks to be recognized by `js2-mode'."
29 :type (cons 'set (mapcar (lambda (x) (list 'const x))
30 js2-imenu-available-frameworks))
31 :group 'js2-mode)
32
33 (defcustom js2-imenu-show-other-functions t
34 "Non-nil to show functions not recognized by other mechanisms,
35 in a shared namespace."
36 :type 'boolean
37 :group 'js2-mode)
38
39 (defcustom js2-imenu-other-functions-ns "?"
40 "Namespace name to use for other functions."
41 :type 'string
42 :group 'js2-mode)
43
44 (defcustom js2-imenu-show-module-pattern t
45 "Non-nil to recognize the module pattern:
46
47 var foobs = (function(a) {
48 return {fib: function() {}, fub: function() {}};
49 })(b);
50
51 We record the returned hash as belonging to the named module, and
52 prefix any functions defined inside the IIFE with the module name."
53 :type 'boolean
54 :group 'js2-mode)
55
56 (defun js2-imenu-extras-setup ()
57 (when js2-imenu-enabled-frameworks
58 (add-to-list 'js2-post-parse-callbacks 'js2-imenu-record-declarations t))
59 (when (or js2-imenu-show-other-functions js2-imenu-show-module-pattern)
60 (add-to-list 'js2-post-parse-callbacks 'js2-imenu-walk-ast t)))
61
62 (declare (special root))
63
64 (defun js2-imenu-record-declarations ()
65 (let* ((styles (loop for style in js2-imenu-extension-styles
66 when (memq (plist-get style :framework)
67 js2-imenu-enabled-frameworks)
68 collect style))
69 (re (mapconcat (lambda (style)
70 (concat "\\(" (plist-get style :call-re) "\\)"))
71 styles "\\|"))
72 ;; Dynamic scoping. Ew.
73 (js2-mode-ast root))
74 (goto-char (point-min))
75 (while (js-re-search-forward re nil t)
76 (loop for i from 0 to (1- (length styles))
77 when (match-beginning (1+ i))
78 return (funcall (plist-get (nth i styles) :recorder))))))
79
80 (defun js2-imenu-record-jquery-extend ()
81 (let ((pred (lambda (subject)
82 (and
83 (js2-prop-get-node-p subject)
84 (string= (js2-name-node-name (js2-prop-get-node-right subject))
85 "prototype")))))
86 (js2-imenu-record-extend-first-arg (1- (point)) pred
87 'js2-compute-nested-prop-get)))
88
89 (defun js2-imenu-record-string-declare ()
90 (js2-imenu-record-extend-first-arg
91 (1- (point)) 'js2-string-node-p
92 (lambda (node) (split-string (js2-string-node-value node) "\\." t))))
93
94 (defun js2-imenu-record-extend-first-arg (point pred qname-fn)
95 (let* ((node (js2-node-at-point point))
96 (args (js2-call-node-args node))
97 (subject (first args)))
98 (when (funcall pred subject)
99 (loop for arg in (cdr args)
100 when (js2-object-node-p arg)
101 do (js2-record-object-literal
102 arg (funcall qname-fn subject) (js2-node-abs-pos arg))))))
103
104 (defun js2-imenu-record-backbone-extend ()
105 (let* ((node (js2-node-at-point (1- (point))))
106 (args (js2-call-node-args node))
107 (methods (first args))
108 (parent (js2-node-parent node)))
109 (when (js2-object-node-p methods)
110 (let ((subject (cond ((js2-var-init-node-p parent)
111 (js2-var-init-node-target parent))
112 ((js2-assign-node-p parent)
113 (js2-assign-node-left parent)))))
114 (when subject
115 (js2-record-object-literal methods
116 (js2-compute-nested-prop-get subject)
117 (js2-node-abs-pos methods)))))))
118
119 (defun js2-imenu-walk-ast ()
120 (js2-visit-ast
121 root
122 (lambda (node end-p)
123 (unless end-p
124 (cond
125 ((and js2-imenu-show-other-functions
126 (js2-object-prop-node-p node))
127 (js2-imenu-record-orphan-function node))
128 ((and js2-imenu-show-module-pattern
129 (js2-assign-node-p node))
130 (js2-imenu-record-module-pattern node)))
131 t))))
132
133 (defun js2-imenu-record-orphan-function (node)
134 "Record orphan function when it's the value of NODE.
135 NODE must be `js2-object-prop-node'."
136 (when (js2-function-node-p (js2-object-prop-node-right node))
137 (let ((fn-node (js2-object-prop-node-right node)))
138 (unless (and js2-imenu-function-map
139 (gethash fn-node js2-imenu-function-map))
140 (let ((key-node (js2-object-prop-node-left node)))
141 (js2-record-imenu-entry fn-node
142 (list js2-imenu-other-functions-ns
143 (js2-prop-node-name key-node))
144 (js2-node-abs-pos key-node)))))))
145
146 (defun js2-imenu-record-module-pattern (node)
147 "Recognize and record module pattern use instance.
148 NODE must be `js2-assign-node'."
149 (let ((init (js2-assign-node-right node)))
150 (when (js2-call-node-p init)
151 (let ((target (js2-assign-node-left node))
152 (callt (js2-call-node-target init)))
153 ;; Just basic call form: (function() {...})();
154 ;; TODO: Handle variations without duplicating `js2-wrapper-function-p'?
155 (when (and (js2-paren-node-p callt)
156 (js2-function-node-p (js2-paren-node-expr callt)))
157 (let* ((fn (js2-paren-node-expr callt))
158 (blk (js2-function-node-body fn))
159 (ret (car (last (js2-block-node-kids blk)))))
160 (when (and (js2-return-node-p ret)
161 (js2-object-node-p (js2-return-node-retval ret)))
162 ;; TODO: Map function names when revealing module pattern is used.
163 (let ((retval (js2-return-node-retval ret)))
164 (js2-record-object-literal retval
165 (js2-compute-nested-prop-get target)
166 (js2-node-abs-pos retval)))
167 (js2-record-imenu-functions fn target))))))))
168
169 (provide 'js2-imenu-extras)