white

rjsx with elpa

1 +;;; js2-imenu-extras.el --- Imenu support for additional constructs
2 +
3 +;; Copyright (C) 2012-2014 Free Software Foundation, Inc.
4 +
5 +;; Author: Dmitry Gutov <dgutov@yandex.ru>
6 +;; Keywords: languages, javascript, imenu
7 +
8 +;; This file is part of GNU Emacs.
9 +
10 +;; GNU Emacs is free software: you can redistribute it and/or modify
11 +;; it under the terms of the GNU General Public License as published by
12 +;; the Free Software Foundation, either version 3 of the License, or
13 +;; (at your option) any later version.
14 +
15 +;; GNU Emacs is distributed in the hope that it will be useful,
16 +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 +;; GNU General Public License for more details.
19 +
20 +;; You should have received a copy of the GNU General Public License
21 +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22 +
23 +;;; Commentary:
24 +
25 +;; This package adds Imenu support for additional framework constructs and
26 +;; structural patterns to `js2-mode'.
27 +
28 +;; Usage:
29 +
30 +;; (add-hook 'js2-mode-hook 'js2-imenu-extras-mode)
31 +
32 +;; To customize how it works:
33 +;; M-x customize-group RET js2-imenu RET
34 +
35 +(eval-when-compile
36 + (require 'cl))
37 +
38 +(require 'js2-mode)
39 +
40 +(defvar js2-imenu-extension-styles
41 + `((:framework jquery
42 + :call-re "\\_<\\(?:jQuery\\|\\$\\|_\\)\\.extend\\s-*("
43 + :recorder js2-imenu-record-jquery-extend)
44 +
45 + (:framework jquery-ui
46 + :call-re "^\\s-*\\(?:jQuery\\|\\$\\)\\.widget\\s-*("
47 + :recorder js2-imenu-record-string-declare)
48 +
49 + (:framework dojo
50 + :call-re "^\\s-*dojo.declare\\s-*("
51 + :recorder js2-imenu-record-string-declare)
52 +
53 + (:framework backbone
54 + :call-re ,(concat "\\_<" js2-mode-identifier-re "\\.extend\\s-*(")
55 + :recorder js2-imenu-record-backbone-extend)
56 +
57 + (:framework enyo
58 + :call-re "\\_<enyo\\.kind\\s-*("
59 + :recorder js2-imenu-record-enyo-kind)
60 +
61 + (:framework react
62 + :call-re "\\_<React\\.createClass\\s-*("
63 + :recorder js2-imenu-record-react-class)
64 +
65 + (:framework sencha
66 + :call-re "^\\s-*Ext\\.define\\s-*("
67 + :recorder js2-imenu-record-sencha-class))
68 + "List of JavaScript class definition or extension styles.
69 +
70 +:framework is a valid value in `js2-imenu-enabled-frameworks'.
71 +
72 +:call-re is a regular expression that has no capturing groups.
73 +
74 +:recorder is a function name that will be called when the regular
75 +expression matches some text in the buffer. When it's called, point will be
76 +at the end of the match. The function must keep the point position.")
77 +
78 +(defconst js2-imenu-available-frameworks
79 + (mapcar (lambda (style) (plist-get style :framework)) js2-imenu-extension-styles)
80 + "List of available JavaScript framework symbols.")
81 +
82 +(defcustom js2-imenu-enabled-frameworks js2-imenu-available-frameworks
83 + "Frameworks to be recognized by `js2-mode'."
84 + :type (cons 'set (mapcar (lambda (x) (list 'const x))
85 + js2-imenu-available-frameworks))
86 + :group 'js2-imenu)
87 +
88 +(defcustom js2-imenu-show-other-functions t
89 + "Non-nil to show functions not recognized by other mechanisms,
90 +in a shared namespace."
91 + :type 'boolean
92 + :group 'js2-imenu)
93 +
94 +(defcustom js2-imenu-other-functions-ns "?"
95 + "Namespace name to use for other functions."
96 + :type 'string
97 + :group 'js2-imenu)
98 +
99 +(defcustom js2-imenu-show-module-pattern t
100 + "Non-nil to recognize the module pattern:
101 +
102 +var foobs = (function(a) {
103 + return {fib: function() {}, fub: function() {}};
104 +})(b);
105 +
106 +We record the returned hash as belonging to the named module, and
107 +prefix any functions defined inside the IIFE with the module name."
108 + :type 'boolean
109 + :group 'js2-imenu)
110 +
111 +(defcustom js2-imenu-split-string-identifiers t
112 + "When non-nil, split string identifiers on dots.
113 +Currently used for jQuery widgets, Dojo and Enyo declarations."
114 + :type 'boolean
115 + :group 'js2-imenu)
116 +
117 +;;;###autoload
118 +(defun js2-imenu-extras-setup ()
119 + (when js2-imenu-enabled-frameworks
120 + (add-hook 'js2-build-imenu-callbacks 'js2-imenu-record-declarations t t))
121 + (when (or js2-imenu-show-other-functions js2-imenu-show-module-pattern)
122 + (add-hook 'js2-build-imenu-callbacks 'js2-imenu-walk-ast t t)))
123 +
124 +(defun js2-imenu-extras-remove ()
125 + (remove-hook 'js2-build-imenu-callbacks 'js2-imenu-record-declarations t)
126 + (remove-hook 'js2-build-imenu-callbacks 'js2-imenu-walk-ast t))
127 +
128 +(defun js2-imenu-record-declarations ()
129 + (let* ((styles (loop for style in js2-imenu-extension-styles
130 + when (memq (plist-get style :framework)
131 + js2-imenu-enabled-frameworks)
132 + collect style))
133 + (re (mapconcat (lambda (style)
134 + (concat "\\(" (plist-get style :call-re) "\\)"))
135 + styles "\\|")))
136 + (goto-char (point-min))
137 + (while (js2-re-search-forward re nil t)
138 + (loop for i from 0 to (1- (length styles))
139 + when (match-beginning (1+ i))
140 + return (funcall (plist-get (nth i styles) :recorder))))))
141 +
142 +(defun js2-imenu-record-jquery-extend ()
143 + (let ((pred (lambda (subject)
144 + (and
145 + (js2-prop-get-node-p subject)
146 + (string= (js2-name-node-name (js2-prop-get-node-right subject))
147 + "prototype")))))
148 + (js2-imenu-record-extend-first-arg (1- (point)) pred
149 + 'js2-compute-nested-prop-get)))
150 +
151 +(defun js2-imenu-record-string-declare ()
152 + (js2-imenu-record-extend-first-arg
153 + (1- (point)) 'js2-string-node-p
154 + (lambda (node)
155 + (if js2-imenu-split-string-identifiers
156 + (split-string (js2-string-node-value node) "\\." t)
157 + (list (js2-string-node-value node))))))
158 +
159 +(defun js2-imenu-record-extend-first-arg (point pred qname-fn)
160 + (let* ((node (js2-node-at-point point))
161 + (args (js2-call-node-args node))
162 + (subject (first args)))
163 + (when (funcall pred subject)
164 + (loop for arg in (cdr args)
165 + when (js2-object-node-p arg)
166 + do (js2-record-object-literal
167 + arg (funcall qname-fn subject) (js2-node-abs-pos arg))))))
168 +
169 +(defun js2-imenu-record-backbone-or-react ()
170 + (let* ((node (js2-node-at-point (1- (point))))
171 + (args (js2-call-node-args node))
172 + (methods (first args))
173 + (parent (js2-node-parent node)))
174 + (when (js2-object-node-p methods)
175 + (let ((subject (cond ((js2-var-init-node-p parent)
176 + (js2-var-init-node-target parent))
177 + ((js2-assign-node-p parent)
178 + (js2-assign-node-left parent)))))
179 + (when subject
180 + (js2-record-object-literal methods
181 + (js2-compute-nested-prop-get subject)
182 + (js2-node-abs-pos methods)))))))
183 +
184 +(defalias 'js2-imenu-record-backbone-extend 'js2-imenu-record-backbone-or-react)
185 +
186 +(defalias 'js2-imenu-record-react-class 'js2-imenu-record-backbone-or-react)
187 +
188 +(defun js2-imenu-record-enyo-kind ()
189 + (let* ((node (js2-node-at-point (1- (point))))
190 + (args (js2-call-node-args node))
191 + (options (first args)))
192 + (when (js2-object-node-p options)
193 + (let ((name-value
194 + (loop for elem in (js2-object-node-elems options)
195 + thereis
196 + (let ((key (js2-object-prop-node-left elem))
197 + (value (js2-object-prop-node-right elem)))
198 + (when (and (equal
199 + (cond ((js2-name-node-p key)
200 + (js2-name-node-name key))
201 + ((js2-string-node-p key)
202 + (js2-string-node-value key)))
203 + "name")
204 + (js2-string-node-p value))
205 + (js2-string-node-value value))))))
206 + (when name-value
207 + (js2-record-object-literal options
208 + (if js2-imenu-split-string-identifiers
209 + (split-string name-value "\\.")
210 + (list name-value))
211 + (js2-node-abs-pos options)))))))
212 +
213 +(defun js2-imenu-record-sencha-class ()
214 + (let* ((node (js2-node-at-point (1- (point))))
215 + (args (js2-call-node-args node))
216 + (name (first args))
217 + (methods (second args)))
218 + (when (and (js2-string-node-p name) (js2-object-node-p methods))
219 + (let ((name-value (js2-string-node-value name)))
220 + (js2-record-object-literal methods
221 + (if js2-imenu-split-string-identifiers
222 + (split-string name-value "\\." t)
223 + (list name-value))
224 + (js2-node-abs-pos methods))))))
225 +
226 +(defun js2-imenu-walk-ast ()
227 + (js2-visit-ast
228 + js2-mode-ast
229 + (lambda (node end-p)
230 + (unless end-p
231 + (cond
232 + ((and js2-imenu-show-other-functions
233 + (js2-object-prop-node-p node))
234 + (js2-imenu-record-orphan-prop-node-function node))
235 + ((js2-assign-node-p node)
236 + (cond
237 + ((and js2-imenu-show-other-functions
238 + (js2-function-node-p
239 + (js2-assign-node-right node)))
240 + (js2-imenu-record-orphan-assign-node-function
241 + (js2-assign-node-left node)
242 + (js2-assign-node-right node)))
243 + ((and js2-imenu-show-module-pattern
244 + (js2-call-node-p
245 + (js2-assign-node-right node)))
246 + (js2-imenu-record-module-pattern
247 + (js2-assign-node-left node)
248 + (js2-assign-node-right node)))))
249 + ((js2-var-init-node-p node)
250 + (cond
251 + ((and js2-imenu-show-other-functions
252 + (js2-function-node-p
253 + (js2-var-init-node-initializer node)))
254 + (js2-imenu-record-orphan-assign-node-function
255 + (js2-var-init-node-target node)
256 + (js2-var-init-node-initializer node)))
257 + ((and js2-imenu-show-module-pattern
258 + (js2-call-node-p
259 + (js2-var-init-node-initializer node)))
260 + (js2-imenu-record-module-pattern
261 + (js2-var-init-node-target node)
262 + (js2-var-init-node-initializer node))))))
263 + t))))
264 +
265 +(defun js2-imenu-parent-key-names (node)
266 + "Get the list of parent key names of NODE.
267 +
268 +For example, for code
269 +
270 + {rules: {password: {required: function() {}}}}
271 +
272 +when NODE is the inner `js2-object-prop-mode',
273 +it returns `(\"rules\" \"password\")'."
274 + (let (rlt (n node))
275 + (while (setq n (js2-imenu-parent-prop-node n))
276 + (push (js2-prop-node-name (js2-object-prop-node-left n)) rlt))
277 + rlt))
278 +
279 +(defun js2-imenu-parent-prop-node (node)
280 + "When the parent of NODE is `js2-object-node',
281 +and the grandparent is `js2-object-prop-node',
282 +return the grandparent."
283 + ;; Suppose the code is:
284 + ;; {parent-key: {required: function() {}}}
285 + ;; NODE is `required: function() {}'.
286 + (let (p2 p3)
287 + ;; Parent is `{required: function() {}}'.
288 + (setq p2 (js2-node-parent node))
289 + ;; GP is `parent-key: {required: function() {}}'.
290 + (when (and p2 (js2-object-node-p p2))
291 + (setq p3 (js2-node-parent p2))
292 + (if (and p3 (js2-object-prop-node-p p3)) p3))))
293 +
294 +(defun js2-imenu-record-orphan-prop-node-function (node)
295 + "Record orphan function when it's the value of NODE.
296 +NODE must be `js2-object-prop-node'."
297 + (when (js2-function-node-p (js2-object-prop-node-right node))
298 + (let ((fn-node (js2-object-prop-node-right node)))
299 + (unless (and js2-imenu-function-map
300 + (gethash fn-node js2-imenu-function-map))
301 + (let ((key-node (js2-object-prop-node-left node))
302 + (parent-prop-node (js2-imenu-parent-prop-node node))
303 + chain)
304 + (setq chain (nconc (js2-imenu-parent-key-names node)
305 + (list (js2-prop-node-name key-node))))
306 + (push js2-imenu-other-functions-ns chain)
307 + (js2-record-imenu-entry fn-node chain
308 + (js2-node-abs-pos key-node)))))))
309 +
310 +(defun js2-imenu-record-orphan-assign-node-function (target-node fn-node)
311 + "Record orphan function FN-NODE assigned to node TARGET."
312 + (when (or (not js2-imenu-function-map)
313 + (eq 'skip
314 + (gethash fn-node js2-imenu-function-map 'skip)))
315 + (let ((chain (js2-compute-nested-prop-get target-node)))
316 + (when chain
317 + (push js2-imenu-other-functions-ns chain)
318 + (js2-record-imenu-entry fn-node chain (js2-node-abs-pos fn-node))))))
319 +
320 +(defun js2-imenu-record-module-pattern (target init)
321 + "Recognize and record module pattern use instance.
322 +INIT must be `js2-call-node'."
323 + (let ((callt (js2-call-node-target init)))
324 + ;; Just basic call form: (function() {...})();
325 + ;; TODO: Handle variations without duplicating `js2-wrapper-function-p'?
326 + (when (and (js2-paren-node-p callt)
327 + (js2-function-node-p (js2-paren-node-expr callt)))
328 + (let* ((fn (js2-paren-node-expr callt))
329 + (blk (js2-function-node-body fn))
330 + (ret (car (last (js2-block-node-kids blk)))))
331 + (when (and (js2-return-node-p ret)
332 + (js2-object-node-p (js2-return-node-retval ret)))
333 + ;; TODO: Map function names when revealing module pattern is used.
334 + (let ((retval (js2-return-node-retval ret))
335 + (target-qname (js2-compute-nested-prop-get target)))
336 + (js2-record-object-literal retval target-qname
337 + (js2-node-abs-pos retval))
338 + (js2-record-imenu-entry fn target-qname
339 + (js2-node-abs-pos target))))))))
340 +
341 +;;;###autoload
342 +(define-minor-mode js2-imenu-extras-mode
343 + "Toggle Imenu support for frameworks and structural patterns."
344 + :lighter ""
345 + (if js2-imenu-extras-mode
346 + (js2-imenu-extras-setup)
347 + (js2-imenu-extras-remove)))
348 +
349 +(provide 'js2-imenu-extras)
1 +;;; js2-mode-autoloads.el --- automatically extracted autoloads
2 +;;
3 +;;; Code:
4 +(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
5 +
6 +;;;### (autoloads nil "js2-imenu-extras" "js2-imenu-extras.el" (22981
7 +;;;;;; 60718 536222 176000))
8 +;;; Generated autoloads from js2-imenu-extras.el
9 +
10 +(autoload 'js2-imenu-extras-setup "js2-imenu-extras" "\
11 +
12 +
13 +\(fn)" nil nil)
14 +
15 +(autoload 'js2-imenu-extras-mode "js2-imenu-extras" "\
16 +Toggle Imenu support for frameworks and structural patterns.
17 +
18 +\(fn &optional ARG)" t nil)
19 +
20 +;;;***
21 +
22 +;;;### (autoloads nil "js2-mode" "js2-mode.el" (22981 60718 552222
23 +;;;;;; 310000))
24 +;;; Generated autoloads from js2-mode.el
25 +
26 +(autoload 'js2-highlight-unused-variables-mode "js2-mode" "\
27 +Toggle highlight of unused variables.
28 +
29 +\(fn &optional ARG)" t nil)
30 +
31 +(autoload 'js2-minor-mode "js2-mode" "\
32 +Minor mode for running js2 as a background linter.
33 +This allows you to use a different major mode for JavaScript editing,
34 +such as `js-mode', while retaining the asynchronous error/warning
35 +highlighting features of `js2-mode'.
36 +
37 +\(fn &optional ARG)" t nil)
38 +
39 +(autoload 'js2-mode "js2-mode" "\
40 +Major mode for editing JavaScript code.
41 +
42 +\(fn)" t nil)
43 +
44 +(autoload 'js2-jsx-mode "js2-mode" "\
45 +Major mode for editing JSX code.
46 +
47 +To customize the indentation for this mode, set the SGML offset
48 +variables (`sgml-basic-offset' et al) locally, like so:
49 +
50 + (defun set-jsx-indentation ()
51 + (setq-local sgml-basic-offset js2-basic-offset))
52 + (add-hook \\='js2-jsx-mode-hook #\\='set-jsx-indentation)
53 +
54 +\(fn)" t nil)
55 +
56 +;;;***
57 +
58 +;;;### (autoloads nil nil ("js2-mode-pkg.el" "js2-old-indent.el")
59 +;;;;;; (22981 60718 574371 921000))
60 +
61 +;;;***
62 +
63 +;; Local Variables:
64 +;; version-control: never
65 +;; no-byte-compile: t
66 +;; no-update-autoloads: t
67 +;; End:
68 +;;; js2-mode-autoloads.el ends here
1 +(define-package "js2-mode" "20170815.1415" "Improved JavaScript editing mode"
2 + '((emacs "24.1")
3 + (cl-lib "0.5"))
4 + :url "https://github.com/mooz/js2-mode/" :keywords
5 + '("languages" "javascript"))
6 +;; Local Variables:
7 +;; no-byte-compile: t
8 +;; End:
This diff could not be displayed because it is too large.
No preview for this file type
1 +;;; js2-old-indent.el --- Indentation code kept for compatibility
2 +
3 +;; Copyright (C) 2015 Free Software Foundation, Inc.
4 +
5 +;; This file is part of GNU Emacs.
6 +
7 +;; GNU Emacs is free software: you can redistribute it and/or modify
8 +;; it under the terms of the GNU General Public License as published by
9 +;; the Free Software Foundation, either version 3 of the License, or
10 +;; (at your option) any later version.
11 +
12 +;; GNU Emacs is distributed in the hope that it will be useful,
13 +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 +;; GNU General Public License for more details.
16 +
17 +;; You should have received a copy of the GNU General Public License
18 +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
19 +
20 +;;; Commentary:
21 +
22 +;; All features of this indentation code have been ported to Emacs's
23 +;; built-in `js-mode' by now, so we derive from it. An older
24 +;; commentary follows.
25 +
26 +;; This code is kept for Emacs 24.5 and ealier.
27 +
28 +;; This indenter is based on Karl Landström's "javascript.el" indenter.
29 +;; Karl cleverly deduces that the desired indentation level is often a
30 +;; function of paren/bracket/brace nesting depth, which can be determined
31 +;; quickly via the built-in `parse-partial-sexp' function. His indenter
32 +;; then does some equally clever checks to see if we're in the context of a
33 +;; substatement of a possibly braceless statement keyword such as if, while,
34 +;; or finally. This approach yields pretty good results.
35 +
36 +;; The indenter is often "wrong", however, and needs to be overridden.
37 +;; The right long-term solution is probably to emulate (or integrate
38 +;; with) cc-engine, but it's a nontrivial amount of coding. Even when a
39 +;; parse tree from `js2-parse' is present, which is not true at the
40 +;; moment the user is typing, computing indentation is still thousands
41 +;; of lines of code to handle every possible syntactic edge case.
42 +
43 +;; In the meantime, the compromise solution is that we offer a "bounce
44 +;; indenter", configured with `js2-bounce-indent-p', which cycles the
45 +;; current line indent among various likely guess points. This approach
46 +;; is far from perfect, but should at least make it slightly easier to
47 +;; move the line towards its desired indentation when manually
48 +;; overriding Karl's heuristic nesting guesser.
49 +
50 +;; I've made miscellaneous tweaks to Karl's code to handle some Ecma
51 +;; extensions such as `let' and Array comprehensions. Major kudos to
52 +;; Karl for coming up with the initial approach, which packs a lot of
53 +;; punch for so little code. -- Steve
54 +
55 +;;; Code:
56 +
57 +(require 'sgml-mode)
58 +
59 +(defvar js2-language-version)
60 +
61 +(declare-function js2-backward-sws "js2-mode")
62 +(declare-function js2-forward-sws "js2-mode")
63 +(declare-function js2-same-line "js2-mode")
64 +
65 +(defcustom js2-basic-offset (if (and (boundp 'c-basic-offset)
66 + (numberp c-basic-offset))
67 + c-basic-offset
68 + 4)
69 + "Number of spaces to indent nested statements.
70 +Similar to `c-basic-offset'."
71 + :group 'js2-mode
72 + :safe 'integerp
73 + :type 'integer)
74 +
75 +(defcustom js2-pretty-multiline-declarations t
76 + "Non-nil to line up multiline declarations vertically:
77 +
78 + var a = 10,
79 + b = 20,
80 + c = 30;
81 +
82 +If the value is t, and the first assigned value in the
83 +declaration is a function/array/object literal spanning several
84 +lines, it won't be indented additionally:
85 +
86 + var o = { var bar = 2,
87 + foo: 3 vs. o = {
88 + }, foo: 3
89 + bar = 2; };
90 +
91 +If the value is `all', it will always be indented additionally:
92 +
93 + var o = {
94 + foo: 3
95 + };
96 +
97 + var o = {
98 + foo: 3
99 + },
100 + bar = 2;
101 +
102 +If the value is `dynamic', it will be indented additionally only
103 +if the declaration contains more than one variable:
104 +
105 + var o = {
106 + foo: 3
107 + };
108 +
109 + var o = {
110 + foo: 3
111 + },
112 + bar = 2;"
113 + :group 'js2-mode
114 + :safe 'symbolp
115 + :type 'symbol)
116 +
117 +(defcustom js2-indent-switch-body nil
118 + "When nil, case labels are indented on the same level as the
119 +containing switch statement. Otherwise, all lines inside
120 +switch statement body are indented one additional level."
121 + :type 'boolean
122 + :safe 'booleanp
123 + :group 'js2-mode)
124 +
125 +(defconst js2-possibly-braceless-keywords-re
126 + (concat "else[ \t]+if\\|for[ \t]+each\\|"
127 + (regexp-opt '("catch" "do" "else" "finally" "for" "if"
128 + "try" "while" "with" "let")))
129 + "Regular expression matching keywords that are optionally
130 +followed by an opening brace.")
131 +
132 +(defconst js2-indent-operator-re
133 + (concat "[-+*/%<>&^|?:.]\\([^-+*/.]\\|$\\)\\|!?=\\|"
134 + (regexp-opt '("in" "instanceof") 'symbols))
135 + "Regular expression matching operators that affect indentation
136 +of continued expressions.")
137 +
138 +(defconst js2-declaration-keyword-re
139 + (regexp-opt '("var" "let" "const") 'symbols)
140 + "Regular expression matching variable declaration keywords.")
141 +
142 +(defun js2-re-search-forward-inner (regexp &optional bound count)
143 + "Auxiliary function for `js2-re-search-forward'."
144 + (let (parse saved-point)
145 + (while (> count 0)
146 + (re-search-forward regexp bound)
147 + (setq parse (if saved-point
148 + (parse-partial-sexp saved-point (point))
149 + (syntax-ppss (point))))
150 + (cond ((nth 3 parse)
151 + (re-search-forward
152 + (concat "\\(\\=\\|[^\\]\\|^\\)" (string (nth 3 parse)))
153 + (save-excursion (end-of-line) (point)) t))
154 + ((nth 7 parse)
155 + (forward-line))
156 + ((or (nth 4 parse)
157 + (and (eq (char-before) ?\/) (eq (char-after) ?\*)))
158 + (re-search-forward "\\*/"))
159 + (t
160 + (setq count (1- count))))
161 + (setq saved-point (point))))
162 + (point))
163 +
164 +(defun js2-re-search-forward (regexp &optional bound noerror count)
165 + "Search forward but ignore strings and comments.
166 +Invokes `re-search-forward' but treats the buffer as if strings
167 +and comments have been removed."
168 + (let ((saved-point (point)))
169 + (condition-case err
170 + (cond ((null count)
171 + (js2-re-search-forward-inner regexp bound 1))
172 + ((< count 0)
173 + (js2-re-search-backward-inner regexp bound (- count)))
174 + ((> count 0)
175 + (js2-re-search-forward-inner regexp bound count)))
176 + (search-failed
177 + (goto-char saved-point)
178 + (unless noerror
179 + (error (error-message-string err)))))))
180 +
181 +(defun js2-re-search-backward-inner (regexp &optional bound count)
182 + "Auxiliary function for `js2-re-search-backward'."
183 + (let (parse)
184 + (while (> count 0)
185 + (re-search-backward regexp bound)
186 + (setq parse (syntax-ppss (point)))
187 + (cond ((nth 3 parse)
188 + (re-search-backward
189 + (concat "\\([^\\]\\|^\\)" (string (nth 3 parse)))
190 + (line-beginning-position) t))
191 + ((nth 7 parse)
192 + (goto-char (nth 8 parse)))
193 + ((or (nth 4 parse)
194 + (and (eq (char-before) ?/) (eq (char-after) ?*)))
195 + (re-search-backward "/\\*"))
196 + (t
197 + (setq count (1- count))))))
198 + (point))
199 +
200 +(defun js2-re-search-backward (regexp &optional bound noerror count)
201 + "Search backward but ignore strings and comments.
202 +Invokes `re-search-backward' but treats the buffer as if strings
203 +and comments have been removed."
204 + (let ((saved-point (point)))
205 + (condition-case err
206 + (cond ((null count)
207 + (js2-re-search-backward-inner regexp bound 1))
208 + ((< count 0)
209 + (js2-re-search-forward-inner regexp bound (- count)))
210 + ((> count 0)
211 + (js2-re-search-backward-inner regexp bound count)))
212 + (search-failed
213 + (goto-char saved-point)
214 + (unless noerror
215 + (error (error-message-string err)))))))
216 +
217 +(defun js2-looking-at-operator-p ()
218 + "Return non-nil if text after point is a non-comma operator."
219 + (defvar js2-mode-identifier-re)
220 + (and (looking-at js2-indent-operator-re)
221 + (or (not (eq (char-after) ?:))
222 + (save-excursion
223 + (and (js2-re-search-backward "[?:{]\\|\\_<case\\_>" nil t)
224 + (eq (char-after) ??))))
225 + (not (and
226 + (eq (char-after) ?/)
227 + (save-excursion
228 + (eq (nth 3 (syntax-ppss)) ?/))))
229 + (not (and
230 + (eq (char-after) ?*)
231 + ;; Generator method (possibly using computed property).
232 + (looking-at (concat "\\* *\\(?:\\[\\|"
233 + js2-mode-identifier-re
234 + " *(\\)"))
235 + (save-excursion
236 + (js2-backward-sws)
237 + ;; We might misindent some expressions that would
238 + ;; return NaN anyway. Shouldn't be a problem.
239 + (memq (char-before) '(?, ?} ?{)))))))
240 +
241 +(defun js2-continued-expression-p ()
242 + "Return non-nil if the current line continues an expression."
243 + (save-excursion
244 + (back-to-indentation)
245 + (if (js2-looking-at-operator-p)
246 + (or (not (memq (char-after) '(?- ?+)))
247 + (progn
248 + (forward-comment (- (point)))
249 + (not (memq (char-before) '(?, ?\[ ?\()))))
250 + (forward-comment (- (point)))
251 + (or (bobp) (backward-char))
252 + (when (js2-looking-at-operator-p)
253 + (backward-char)
254 + (not (looking-at "\\*\\|\\+\\+\\|--\\|/[/*]"))))))
255 +
256 +(defun js2-end-of-do-while-loop-p ()
257 + "Return non-nil if word after point is `while' of a do-while
258 +statement, else returns nil. A braceless do-while statement
259 +spanning several lines requires that the start of the loop is
260 +indented to the same column as the current line."
261 + (interactive)
262 + (save-excursion
263 + (when (looking-at "\\s-*\\_<while\\_>")
264 + (if (save-excursion
265 + (skip-chars-backward "[ \t\n]*}")
266 + (looking-at "[ \t\n]*}"))
267 + (save-excursion
268 + (backward-list) (backward-word 1) (looking-at "\\_<do\\_>"))
269 + (js2-re-search-backward "\\_<do\\_>" (point-at-bol) t)
270 + (or (looking-at "\\_<do\\_>")
271 + (let ((saved-indent (current-indentation)))
272 + (while (and (js2-re-search-backward "^[ \t]*\\_<" nil t)
273 + (/= (current-indentation) saved-indent)))
274 + (and (looking-at "[ \t]*\\_<do\\_>")
275 + (not (js2-re-search-forward
276 + "\\_<while\\_>" (point-at-eol) t))
277 + (= (current-indentation) saved-indent))))))))
278 +
279 +(defun js2-multiline-decl-indentation ()
280 + "Return the declaration indentation column if the current line belongs
281 +to a multiline declaration statement. See `js2-pretty-multiline-declarations'."
282 + (let (forward-sexp-function ; use Lisp version
283 + at-opening-bracket)
284 + (save-excursion
285 + (back-to-indentation)
286 + (when (not (looking-at js2-declaration-keyword-re))
287 + (when (looking-at js2-indent-operator-re)
288 + (goto-char (match-end 0))) ; continued expressions are ok
289 + (while (and (not at-opening-bracket)
290 + (not (bobp))
291 + (let ((pos (point)))
292 + (save-excursion
293 + (js2-backward-sws)
294 + (or (eq (char-before) ?,)
295 + (and (not (eq (char-before) ?\;))
296 + (prog2 (skip-syntax-backward ".")
297 + (looking-at js2-indent-operator-re)
298 + (js2-backward-sws))
299 + (not (eq (char-before) ?\;)))
300 + (js2-same-line pos)))))
301 + (condition-case _
302 + (backward-sexp)
303 + (scan-error (setq at-opening-bracket t))))
304 + (when (looking-at js2-declaration-keyword-re)
305 + (goto-char (match-end 0))
306 + (1+ (current-column)))))))
307 +
308 +(defun js2-ctrl-statement-indentation ()
309 + "Return the proper indentation of current line if it is a control statement.
310 +Returns an indentation if this line starts the body of a control
311 +statement without braces, else returns nil."
312 + (let (forward-sexp-function)
313 + (save-excursion
314 + (back-to-indentation)
315 + (when (and (not (js2-same-line (point-min)))
316 + (not (looking-at "{"))
317 + (js2-re-search-backward "[[:graph:]]" nil t)
318 + (not (looking-at "[{([]"))
319 + (progn
320 + (forward-char)
321 + (when (= (char-before) ?\))
322 + ;; scan-sexps sometimes throws an error
323 + (ignore-errors (backward-sexp))
324 + (skip-chars-backward " \t" (point-at-bol)))
325 + (let ((pt (point)))
326 + (back-to-indentation)
327 + (when (looking-at "}[ \t]*")
328 + (goto-char (match-end 0)))
329 + (and (looking-at js2-possibly-braceless-keywords-re)
330 + (= (match-end 0) pt)
331 + (not (js2-end-of-do-while-loop-p))))))
332 + (+ (current-indentation) js2-basic-offset)))))
333 +
334 +(defun js2-indent-in-array-comp (parse-status)
335 + "Return non-nil if we think we're in an array comprehension.
336 +In particular, return the buffer position of the first `for' kwd."
337 + (let ((bracket (nth 1 parse-status))
338 + (end (point)))
339 + (when bracket
340 + (save-excursion
341 + (goto-char bracket)
342 + (when (looking-at "\\[")
343 + (forward-char 1)
344 + (js2-forward-sws)
345 + (if (looking-at "[[{]")
346 + (let (forward-sexp-function) ; use Lisp version
347 + (forward-sexp) ; skip destructuring form
348 + (js2-forward-sws)
349 + (if (and (/= (char-after) ?,) ; regular array
350 + (looking-at "for"))
351 + (match-beginning 0)))
352 + ;; to skip arbitrary expressions we need the parser,
353 + ;; so we'll just guess at it.
354 + (if (and (> end (point)) ; not empty literal
355 + (re-search-forward "[^,]]* \\(for\\) " end t)
356 + ;; not inside comment or string literal
357 + (let ((state (parse-partial-sexp bracket (point))))
358 + (not (or (nth 3 state) (nth 4 state)))))
359 + (match-beginning 1))))))))
360 +
361 +(defun js2-array-comp-indentation (parse-status for-kwd)
362 + (if (js2-same-line for-kwd)
363 + ;; first continuation line
364 + (save-excursion
365 + (goto-char (nth 1 parse-status))
366 + (forward-char 1)
367 + (skip-chars-forward " \t")
368 + (current-column))
369 + (save-excursion
370 + (goto-char for-kwd)
371 + (current-column))))
372 +
373 +(defun js2-maybe-goto-declaration-keyword-end (bracket)
374 + "Helper function for `js2-proper-indentation'.
375 +Depending on the value of `js2-pretty-multiline-declarations',
376 +move point to the end of a variable declaration keyword so that
377 +indentation is aligned to that column."
378 + (cond
379 + ((eq js2-pretty-multiline-declarations 'all)
380 + (when (looking-at js2-declaration-keyword-re)
381 + (goto-char (1+ (match-end 0)))))
382 + ((eq js2-pretty-multiline-declarations 'dynamic)
383 + (let (declaration-keyword-end
384 + at-closing-bracket-p
385 + comma-p)
386 + (when (looking-at js2-declaration-keyword-re)
387 + ;; Preserve the match data lest it somehow be overridden.
388 + (setq declaration-keyword-end (match-end 0))
389 + (save-excursion
390 + (goto-char bracket)
391 + (setq at-closing-bracket-p
392 + ;; Handle scan errors gracefully.
393 + (condition-case nil
394 + (progn
395 + ;; Use the regular `forward-sexp-function' because the
396 + ;; normal one for this mode uses the AST.
397 + (let (forward-sexp-function)
398 + (forward-sexp))
399 + t)
400 + (error nil)))
401 + (when at-closing-bracket-p
402 + (js2-forward-sws)
403 + (setq comma-p (looking-at-p ","))))
404 + (when comma-p
405 + (goto-char (1+ declaration-keyword-end))))))))
406 +
407 +(cl-defun js2-proper-indentation (parse-status)
408 + "Return the proper indentation for the current line."
409 + (save-excursion
410 + (back-to-indentation)
411 + (when (nth 4 parse-status)
412 + (cl-return-from js2-proper-indentation (js2--comment-indent parse-status)))
413 + (let* ((at-closing-bracket (looking-at "[]})]"))
414 + (same-indent-p (or at-closing-bracket
415 + (looking-at "\\_<case\\_>[^:]")
416 + (and (looking-at "\\_<default:")
417 + (save-excursion
418 + (js2-backward-sws)
419 + (not (memq (char-before) '(?, ?{)))))))
420 + (continued-expr-p (js2-continued-expression-p))
421 + (declaration-indent (and js2-pretty-multiline-declarations
422 + (js2-multiline-decl-indentation)))
423 + (bracket (nth 1 parse-status))
424 + beg indent)
425 + (cond
426 + ;; indent array comprehension continuation lines specially
427 + ((and bracket
428 + (>= js2-language-version 170)
429 + (not (js2-same-line bracket))
430 + (setq beg (js2-indent-in-array-comp parse-status))
431 + (>= (point) (save-excursion
432 + (goto-char beg)
433 + (point-at-bol)))) ; at or after first loop?
434 + (js2-array-comp-indentation parse-status beg))
435 +
436 + ((js2-ctrl-statement-indentation))
437 +
438 + ((and declaration-indent continued-expr-p)
439 + (+ declaration-indent js2-basic-offset))
440 +
441 + (declaration-indent)
442 +
443 + (bracket
444 + (goto-char bracket)
445 + (cond
446 + ((looking-at "[({[][ \t]*\\(/[/*]\\|$\\)")
447 + (when (save-excursion (skip-chars-backward " \t\n)")
448 + (looking-at ")"))
449 + (backward-list))
450 + (back-to-indentation)
451 + (js2-maybe-goto-declaration-keyword-end bracket)
452 + (setq indent
453 + (cond (same-indent-p
454 + (current-column))
455 + (continued-expr-p
456 + (+ (current-column) (* 2 js2-basic-offset)))
457 + (t
458 + (+ (current-column) js2-basic-offset))))
459 + (if (and js2-indent-switch-body
460 + (not at-closing-bracket)
461 + (looking-at "\\_<switch\\_>"))
462 + (+ indent js2-basic-offset)
463 + indent))
464 + (t
465 + (unless same-indent-p
466 + (forward-char)
467 + (skip-chars-forward " \t"))
468 + (current-column))))
469 +
470 + (continued-expr-p js2-basic-offset)
471 +
472 + (t 0)))))
473 +
474 +(defun js2--comment-indent (parse-status)
475 + "Indentation inside a multi-line block comment continuation line."
476 + (save-excursion
477 + (goto-char (nth 8 parse-status))
478 + (if (looking-at "/\\*")
479 + (+ 1 (current-column))
480 + 0)))
481 +
482 +(defun js2-indent-line (&optional bounce-backwards)
483 + "Indent the current line as JavaScript source text."
484 + (interactive)
485 + (let (parse-status offset
486 + ;; Don't whine about errors/warnings when we're indenting.
487 + ;; This has to be set before calling parse-partial-sexp below.
488 + (inhibit-point-motion-hooks t))
489 + (setq parse-status (save-excursion
490 + (syntax-ppss (point-at-bol)))
491 + offset (- (point) (save-excursion
492 + (back-to-indentation)
493 + (point))))
494 + ;; Don't touch multiline strings.
495 + (unless (nth 3 parse-status)
496 + (indent-line-to (js2-proper-indentation parse-status))
497 + (when (cl-plusp offset)
498 + (forward-char offset)))))
499 +
500 +;;; JSX Indentation
501 +
502 +;; The following JSX indentation code is copied basically verbatim from js.el at
503 +;; 958da7f, except that the prefixes on the functions/variables are changed.
504 +
505 +(defsubst js2--jsx-find-before-tag ()
506 + "Find where JSX starts.
507 +
508 +Assume JSX appears in the following instances:
509 +- Inside parentheses, when returned or as the first argument
510 + to a function, and after a newline
511 +- When assigned to variables or object properties, but only
512 + on a single line
513 +- As the N+1th argument to a function
514 +
515 +This is an optimized version of (re-search-backward \"[(,]\n\"
516 +nil t), except set point to the end of the match. This logic
517 +executes up to the number of lines in the file, so it should be
518 +really fast to reduce that impact."
519 + (let (pos)
520 + (while (and (> (point) (point-min))
521 + (not (progn
522 + (end-of-line 0)
523 + (when (or (eq (char-before) 40) ; (
524 + (eq (char-before) 44)) ; ,
525 + (setq pos (1- (point))))))))
526 + pos))
527 +
528 +(defconst js2--jsx-end-tag-re
529 + (concat "</" sgml-name-re ">\\|/>")
530 + "Find the end of a JSX element.")
531 +
532 +(defconst js2--jsx-after-tag-re "[),]"
533 + "Find where JSX ends.
534 +This complements the assumption of where JSX appears from
535 +`js--jsx-before-tag-re', which see.")
536 +
537 +(defun js2--jsx-indented-element-p ()
538 + "Determine if/how the current line should be indented as JSX.
539 +
540 +Return `first' for the first JSXElement on its own line.
541 +Return `nth' for subsequent lines of the first JSXElement.
542 +Return `expression' for an embedded JS expression.
543 +Return `after' for anything after the last JSXElement.
544 +Return nil for non-JSX lines.
545 +
546 +Currently, JSX indentation supports the following styles:
547 +
548 +- Single-line elements (indented like normal JS):
549 +
550 + var element = <div></div>;
551 +
552 +- Multi-line elements (enclosed in parentheses):
553 +
554 + function () {
555 + return (
556 + <div>
557 + <div></div>
558 + </div>
559 + );
560 + }
561 +
562 +- Function arguments:
563 +
564 + React.render(
565 + <div></div>,
566 + document.querySelector('.root')
567 + );"
568 + (let ((current-pos (point))
569 + (current-line (line-number-at-pos))
570 + last-pos
571 + before-tag-pos before-tag-line
572 + tag-start-pos tag-start-line
573 + tag-end-pos tag-end-line
574 + after-tag-line
575 + parens paren type)
576 + (save-excursion
577 + (and
578 + ;; Determine if we're inside a jsx element
579 + (progn
580 + (end-of-line)
581 + (while (and (not tag-start-pos)
582 + (setq last-pos (js2--jsx-find-before-tag)))
583 + (while (forward-comment 1))
584 + (when (= (char-after) 60) ; <
585 + (setq before-tag-pos last-pos
586 + tag-start-pos (point)))
587 + (goto-char last-pos))
588 + tag-start-pos)
589 + (progn
590 + (setq before-tag-line (line-number-at-pos before-tag-pos)
591 + tag-start-line (line-number-at-pos tag-start-pos))
592 + (and
593 + ;; A "before" line which also starts an element begins with js, so
594 + ;; indent it like js
595 + (> current-line before-tag-line)
596 + ;; Only indent the jsx lines like jsx
597 + (>= current-line tag-start-line)))
598 + (cond
599 + ;; Analyze bounds if there are any
600 + ((progn
601 + (while (and (not tag-end-pos)
602 + (setq last-pos (re-search-forward js2--jsx-end-tag-re nil t)))
603 + (while (forward-comment 1))
604 + (when (looking-at js2--jsx-after-tag-re)
605 + (setq tag-end-pos last-pos)))
606 + tag-end-pos)
607 + (setq tag-end-line (line-number-at-pos tag-end-pos)
608 + after-tag-line (line-number-at-pos after-tag-line))
609 + (or (and
610 + ;; Ensure we're actually within the bounds of the jsx
611 + (<= current-line tag-end-line)
612 + ;; An "after" line which does not end an element begins with
613 + ;; js, so indent it like js
614 + (<= current-line after-tag-line))
615 + (and
616 + ;; Handle another case where there could be e.g. comments after
617 + ;; the element
618 + (> current-line tag-end-line)
619 + (< current-line after-tag-line)
620 + (setq type 'after))))
621 + ;; They may not be any bounds (yet)
622 + (t))
623 + ;; Check if we're inside an embedded multi-line js expression
624 + (cond
625 + ((not type)
626 + (goto-char current-pos)
627 + (end-of-line)
628 + (setq parens (nth 9 (syntax-ppss)))
629 + (while (and parens (not type))
630 + (setq paren (car parens))
631 + (cond
632 + ((and (>= paren tag-start-pos)
633 + ;; Curly bracket indicates the start of an embedded expression
634 + (= (char-after paren) 123) ; {
635 + ;; The first line of the expression is indented like sgml
636 + (> current-line (line-number-at-pos paren))
637 + ;; Check if within a closing curly bracket (if any)
638 + ;; (exclusive, as the closing bracket is indented like sgml)
639 + (cond
640 + ((progn
641 + (goto-char paren)
642 + (ignore-errors (let (forward-sexp-function)
643 + (forward-sexp))))
644 + (< current-line (line-number-at-pos)))
645 + (t)))
646 + ;; Indicate this guy will be indented specially
647 + (setq type 'expression))
648 + (t (setq parens (cdr parens)))))
649 + t)
650 + (t))
651 + (cond
652 + (type)
653 + ;; Indent the first jsx thing like js so we can indent future jsx things
654 + ;; like sgml relative to the first thing
655 + ((= current-line tag-start-line) 'first)
656 + ('nth))))))
657 +
658 +(defmacro js2--as-sgml (&rest body)
659 + "Execute BODY as if in sgml-mode."
660 + `(with-syntax-table sgml-mode-syntax-table
661 + (let (forward-sexp-function
662 + parse-sexp-lookup-properties)
663 + ,@body)))
664 +
665 +(defun js2--expression-in-sgml-indent-line ()
666 + "Indent the current line as JavaScript or SGML (whichever is farther)."
667 + (let* (indent-col
668 + (savep (point))
669 + ;; Don't whine about errors/warnings when we're indenting.
670 + ;; This has to be set before calling parse-partial-sexp below.
671 + (inhibit-point-motion-hooks t)
672 + (parse-status (save-excursion
673 + (syntax-ppss (point-at-bol)))))
674 + ;; Don't touch multiline strings.
675 + (unless (nth 3 parse-status)
676 + (setq indent-col (save-excursion
677 + (back-to-indentation)
678 + (if (>= (point) savep) (setq savep nil))
679 + (js2--as-sgml (sgml-calculate-indent))))
680 + (if (null indent-col)
681 + 'noindent
682 + ;; Use whichever indentation column is greater, such that the sgml
683 + ;; column is effectively a minimum
684 + (setq indent-col (max (js2-proper-indentation parse-status)
685 + (+ indent-col js2-basic-offset)))
686 + (if savep
687 + (save-excursion (indent-line-to indent-col))
688 + (indent-line-to indent-col))))))
689 +
690 +(defun js2-jsx-indent-line ()
691 + "Indent the current line as JSX (with SGML offsets).
692 +i.e., customize JSX element indentation with `sgml-basic-offset'
693 +et al."
694 + (interactive)
695 + (let ((indentation-type (js2--jsx-indented-element-p)))
696 + (cond
697 + ((eq indentation-type 'expression)
698 + (js2--expression-in-sgml-indent-line))
699 + ((or (eq indentation-type 'first)
700 + (eq indentation-type 'after))
701 + ;; Don't treat this first thing as a continued expression (often a "<" or
702 + ;; ">" causes this misinterpretation)
703 + (cl-letf (((symbol-function #'js2-continued-expression-p) 'ignore))
704 + (js2-indent-line)))
705 + ((eq indentation-type 'nth)
706 + (js2--as-sgml (sgml-indent-line)))
707 + (t (js2-indent-line)))))
708 +
709 +(provide 'js2-old-indent)
710 +
711 +;;; js2-old-indent.el ends here
No preview for this file type
1 +;;; rjsx-mode-autoloads.el --- automatically extracted autoloads
2 +;;
3 +;;; Code:
4 +(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
5 +
6 +;;;### (autoloads nil "rjsx-mode" "rjsx-mode.el" (22981 60721 372245
7 +;;;;;; 990000))
8 +;;; Generated autoloads from rjsx-mode.el
9 +
10 +(autoload 'rjsx-mode "rjsx-mode" "\
11 +Major mode for editing JSX files.
12 +
13 +\(fn)" t nil)
14 +
15 +(add-to-list 'auto-mode-alist '("\\.jsx\\'" . rjsx-mode))
16 +
17 +;;;***
18 +
19 +;; Local Variables:
20 +;; version-control: never
21 +;; no-byte-compile: t
22 +;; no-update-autoloads: t
23 +;; End:
24 +;;; rjsx-mode-autoloads.el ends here
1 +(define-package "rjsx-mode" "20170808.634" "Real support for JSX" '((emacs "24.4") (js2-mode "20170504")) :commit "4a24c86a1873289538134fe431e544fa3e12e788" :url "https://github.com/felipeochoa/rjsx-mode/" :keywords '("languages"))
No preview for this file type
1 +;;; rjsx-mode.el --- Real support for JSX -*- lexical-binding: t -*-
2 +
3 +;; Copyright (C) 2016 Felipe Ochoa
4 +
5 +;; Author: Felipe Ochoa <felipe@fov.space>
6 +;; URL: https://github.com/felipeochoa/rjsx-mode/
7 +;; Package-Version: 20170808.634
8 +;; Package-Requires: ((emacs "24.4") (js2-mode "20170504"))
9 +;; Version: 1.1
10 +;; Keywords: languages
11 +
12 +;;; Commentary:
13 +;; Defines a major mode `rjsx-mode' based on `js2-mode' for editing
14 +;; JSX files. `rjsx-mode' extends the parser in `js2-mode' to support
15 +;; the full JSX syntax. This means you get all of the `js2' features
16 +;; plus proper syntax checking and highlighting of JSX code blocks.
17 +;;
18 +;; Some features that this mode adds to js2:
19 +;;
20 +;; - Highlighting JSX tag names and attributes (using the rjsx-tag and
21 +;; rjsx-attr faces)
22 +;; - Highlight undeclared JSX components
23 +;; - Parsing the spread operator {...otherProps}
24 +;; - Parsing && and || in child expressions {cond && <BigComponent/>}
25 +;; - Parsing ternary expressions {toggle ? <ToggleOn /> : <ToggleOff />}
26 +;;
27 +;; Additionally, since rjsx-mode extends the js2 AST, utilities using
28 +;; the parse tree gain access to the JSX structure.
29 +
30 +;;; Code:
31 +
32 +;;;; Basic mode definitions
33 +
34 +(require 'cl-lib)
35 +(require 'js2-mode)
36 +
37 +(defgroup rjsx-mode nil
38 + "Support for JSX."
39 + :group 'js2-mode)
40 +
41 +;;;###autoload
42 +(define-derived-mode rjsx-mode js2-jsx-mode "RJSX"
43 + "Major mode for editing JSX files."
44 + :lighter ":RJSX"
45 + :group 'rjsx-mode)
46 +
47 +;;;###autoload
48 +(add-to-list 'auto-mode-alist '("\\.jsx\\'" . rjsx-mode))
49 +
50 +(defun rjsx-parse-xml-initializer (orig-fun)
51 + "Dispatch the xml parser based on variable `rjsx-mode' being active or not.
52 +This function is used to advise `js2-parse-xml-initializer' (ORIG-FUN) using
53 +the `:around' combinator. JS2-PARSER is the original XML parser."
54 + (if (eq major-mode 'rjsx-mode)
55 + (rjsx-parse-top-xml)
56 + (apply orig-fun nil)))
57 +
58 +(advice-add 'js2-parse-xml-initializer :around #'rjsx-parse-xml-initializer)
59 +
60 +(defun rjsx-unadvice-js2 ()
61 + "Remove the rjsx advice on the js2 parser. This will cause rjsx to stop working globally."
62 + (advice-remove 'js2-parse-xml-initializer #'rjsx-parse-xml-initializer))
63 +
64 +
65 +(defface rjsx-tag
66 + '((t . (:inherit font-lock-function-name-face)))
67 + "`rjsx-mode' face used to highlight JSX tag names."
68 + :group 'rjsx-mode)
69 +
70 +(defface rjsx-attr
71 + '((t . (:inherit font-lock-variable-name-face)))
72 + "`rjsx-mode' face used to highlight JSX attribute names."
73 + :group 'rjsx-mode)
74 +
75 +(defface rjsx-text
76 + '((t . (:inherit font-lock-string-face)))
77 + "`rjsx-mode' face used to highlight JSX text."
78 + :group 'rjsx-mode)
79 +
80 +
81 +;;;; Parser constants struct definitions
82 +
83 +;; Token types for XML nodes. We need to re-use some unused values to
84 +;; not mess up the vectors that js2 has set up
85 +(defvar rjsx-JSX js2-ENUM_INIT_KEYS)
86 +(defvar rjsx-JSX-CLOSE js2-ENUM_INIT_VALUES)
87 +(defvar rjsx-JSX-IDENT js2-ENUM_INIT_ARRAY)
88 +(defvar rjsx-JSX-MEMBER js2-ENUM_NEXT)
89 +(defvar rjsx-JSX-ATTR js2-ENUM_ID)
90 +(defvar rjsx-JSX-SPREAD js2-REF_NS_MEMBER)
91 +(defvar rjsx-JSX-TEXT js2-ESCXMLTEXT)
92 +(defvar rjsx-JSX-EXPRESSION js2-ESCXMLATTR)
93 +
94 +(dolist (sym '(rjsx-JSX rjsx-JSX-CLOSE rjsx-JSX-IDENT rjsx-JSX-MEMBER rjsx-JSX-ATTR
95 + rjsx-JSX-SPREAD rjsx-JSX-TEXT rjsx-JSX-EXPRESSION))
96 + (aset js2-token-names (symbol-value sym) (downcase (substring (symbol-name sym) 5)))
97 + (puthash sym (symbol-value sym) js2-token-codes))
98 +
99 +(js2-msg "msg.bad.jsx.ident" "invalid JSX identifier")
100 +(js2-msg "msg.invalid.jsx.string" "invalid JSX string (cannot contain delimiter in string body)")
101 +(js2-msg "msg.mismatched.close.tag" "mismatched closing JSX tag; expected `%s'")
102 +(js2-msg "msg.no.gt.in.opener" "missing `>' in opening tag")
103 +(js2-msg "msg.no.gt.in.closer" "missing `>' in closing tag")
104 +(js2-msg "msg.no.gt.after.slash" "missing `>' after `/' in self-closing tag")
105 +(js2-msg "msg.no.rc.after.spread" "missing `}' after spread-prop")
106 +(js2-msg "msg.no.value.after.jsx.prop" "missing value after prop `%s'")
107 +(js2-msg "msg.no.dots.in.prop.spread" "missing `...' in spread prop")
108 +(js2-msg "msg.no.rc.after.expr" "missing `}' after expression")
109 +(js2-msg "msg.empty.expr" "empty `{}' expression")
110 +
111 +
112 +(cl-defstruct (rjsx-node
113 + (:include js2-node (type rjsx-JSX))
114 + (:constructor nil)
115 + (:constructor make-rjsx-node
116 + (&key (pos (js2-current-token-beg))
117 + len
118 + name
119 + rjsx-props
120 + kids)))
121 + name ; AST node containing the parsed xml name
122 + rjsx-props ; linked list of AST nodes (both attributes and spreads)
123 + kids ; linked list of child xml nodes
124 + closing-tag) ; AST node with the tag closer
125 +
126 +
127 +(js2--struct-put 'rjsx-node 'js2-visitor 'rjsx-node-visit)
128 +(js2--struct-put 'rjsx-node 'js2-printer 'rjsx-node-print)
129 +(defun rjsx-node-visit (ast callback)
130 + "Visit the `rjsx-node' children of AST, invoking CALLBACK on them."
131 + (js2-visit-ast (rjsx-node-name ast) callback)
132 + (dolist (prop (rjsx-node-rjsx-props ast))
133 + (js2-visit-ast prop callback))
134 + (dolist (prop (rjsx-node-kids ast))
135 + (js2-visit-ast prop callback))
136 + (when (rjsx-node-closing-tag ast)
137 + (js2-visit-ast (rjsx-node-closing-tag ast) callback)))
138 +
139 +(defun rjsx-node-print (node indent-level)
140 + "Print the `rjsx-node' NODE at indent level INDENT-LEVEL."
141 + (insert (js2-make-pad indent-level) "<")
142 + (js2-print-ast (rjsx-node-name node) 0)
143 + (dolist (attr (rjsx-node-rjsx-props node))
144 + (insert " ")
145 + (js2-print-ast attr 0))
146 + (let ((closer (rjsx-node-closing-tag node)))
147 + (if (null closer)
148 + (insert "/>")
149 + (insert ">")
150 + (dolist (child (rjsx-node-kids node))
151 + (js2-print-ast child 0))
152 + (js2-print-ast closer indent-level))))
153 +
154 +(defun rjsx-node-opening-tag-name (node)
155 + "Return a string with NODE's opening tag including any namespace and member operations."
156 + (let ((name-n (rjsx-node-name node)))
157 + (cond
158 + ((rjsx-member-p name-n) (rjsx-member-full-name name-n))
159 + ((rjsx-identifier-p name-n) (rjsx-identifier-full-name name-n))
160 + ;; Otherwise it's either nil or an error. Either way, no name :(
161 + (t ""))))
162 +
163 +(defun rjsx-node-push-prop (n rjsx-prop)
164 + "Extend rjsx-node N's rjsx-props with js2-node RJSX-PROP.
165 +Sets JSX-PROPS's parent to N."
166 + (let ((rjsx-props (rjsx-node-rjsx-props n)))
167 + (if rjsx-props
168 + (setcdr rjsx-props (nconc (cdr rjsx-props) (list rjsx-prop)))
169 + (setf (rjsx-node-rjsx-props n) (list rjsx-prop))))
170 + (js2-node-add-children n rjsx-prop))
171 +
172 +(defun rjsx-node-push-child (n kid)
173 + "Extend rjsx-node N's children with js2-node KID.
174 +Sets KID's parent to N."
175 + (let ((kids (rjsx-node-kids n)))
176 + (if kids
177 + (setcdr kids (nconc (cdr kids) (list kid)))
178 + (setf (rjsx-node-kids n) (list kid))))
179 + (js2-node-add-children n kid))
180 +
181 +
182 +(cl-defstruct (rjsx-closing-tag
183 + (:include js2-node (type rjsx-JSX-CLOSE))
184 + (:constructor nil)
185 + (:constructor make-rjsx-closing-tag (&key pos len name)))
186 + name) ; A rjsx-identifier or rjsx-member node
187 +
188 +(js2--struct-put 'rjsx-closing-tag 'js2-visitor 'rjsx-closing-tag-visit)
189 +(js2--struct-put 'rjsx-closing-tag 'js2-printer 'rjsx-closing-tag-print)
190 +
191 +(defun rjsx-closing-tag-visit (ast callback)
192 + "Visit the `rjsx-closing-tag' children of AST, invoking CALLBACK on them."
193 + (js2-visit-ast (rjsx-closing-tag-name ast) callback))
194 +
195 +(defun rjsx-closing-tag-print (node indent-level)
196 + "Print the `rjsx-closing-tag' NODE at INDENT-LEVEL."
197 + (insert (js2-make-pad indent-level) "</" (rjsx-closing-tag-full-name node) ">"))
198 +
199 +(defun rjsx-closing-tag-full-name (n)
200 + "Return the string with N's fully-namespaced name, or just name if it's not namespaced."
201 + (let ((child (rjsx-closing-tag-name n)))
202 + (cond
203 + ((rjsx-member-p child) (rjsx-member-full-name child))
204 + ((rjsx-identifier-p child) (rjsx-identifier-full-name child))
205 + (t ""))))
206 +
207 +(cl-defstruct (rjsx-identifier
208 + (:include js2-node (type rjsx-JSX-IDENT))
209 + (:constructor nil)
210 + (:constructor make-rjsx-identifier (&key (pos (js2-current-token-beg))
211 + len namespace name)))
212 + (namespace nil)
213 + name) ; js2-name-node
214 +
215 +(js2--struct-put 'rjsx-identifier 'js2-visitor 'js2-visit-none)
216 +(js2--struct-put 'rjsx-identifier 'js2-printer 'rjsx-identifier-print)
217 +
218 +(defun rjsx-identifier-print (node indent-level)
219 + "Print the `rjsx-identifier' NODE at INDENT-LEVEL."
220 + (insert (js2-make-pad indent-level) (rjsx-identifier-full-name node)))
221 +
222 +(defun rjsx-identifier-full-name (n)
223 + "Return the string with N's fully-namespaced name, or just name if it's not namespaced."
224 + (if (rjsx-identifier-namespace n)
225 + (format "%s:%s" (rjsx-identifier-namespace n) (js2-name-node-name (rjsx-identifier-name n)))
226 + (js2-name-node-name (rjsx-identifier-name n))))
227 +
228 +(cl-defstruct (rjsx-member
229 + (:include js2-node (type rjsx-JSX-MEMBER))
230 + (:constructor nil)
231 + (:constructor make-rjsx-member (&key pos len dots-pos idents)))
232 + dots-pos ; List of positions of each dot
233 + idents) ; List of rjsx-identifier nodes
234 +
235 +(js2--struct-put 'rjsx-member 'js2-visitor 'js2-visit-none)
236 +(js2--struct-put 'rjsx-member 'js2-printer 'rjsx-member-print)
237 +
238 +(defun rjsx-member-print (node indent-level)
239 + "Print the `rjsx-member' NODE at INDENT-LEVEL."
240 + (insert (js2-make-pad indent-level) (rjsx-member-full-name node)))
241 +
242 +(defun rjsx-member-full-name (n)
243 + "Return the string with N's combined names together."
244 + (mapconcat 'rjsx-identifier-full-name (rjsx-member-idents n) "."))
245 +
246 +(cl-defstruct (rjsx-attr
247 + (:include js2-node (type rjsx-JSX-ATTR))
248 + (:constructor nil)
249 + (:constructor make-rjsx-attr (&key (pos (js2-current-token-beg))
250 + len name value)))
251 + name ; a rjsx-identifier
252 + value) ; a js2-expression
253 +
254 +(js2--struct-put 'rjsx-attr 'js2-visitor 'rjsx-attr-visit)
255 +(js2--struct-put 'rjsx-attr 'js2-printer 'rjsx-attr-print)
256 +
257 +(defun rjsx-attr-visit (ast callback)
258 + "Visit the `rjsx-attr' children of AST, invoking CALLBACK on them."
259 + (js2-visit-ast (rjsx-attr-name ast) callback)
260 + (js2-visit-ast (rjsx-attr-value ast) callback))
261 +
262 +(defun rjsx-attr-print (node indent-level)
263 + "Print the `rjsx-attr' NODE at INDENT-LEVEL."
264 + (js2-print-ast (rjsx-attr-name node) indent-level)
265 + (unless (js2-empty-expr-node-p (rjsx-attr-value node))
266 + (insert "=")
267 + (js2-print-ast (rjsx-attr-value node) 0)))
268 +
269 +(cl-defstruct (rjsx-spread
270 + (:include js2-node (type rjsx-JSX-SPREAD))
271 + (:constructor nil)
272 + (:constructor make-rjsx-spread (&key pos len expr)))
273 + expr) ; a js2-expression
274 +
275 +(js2--struct-put 'rjsx-spread 'js2-visitor 'rjsx-spread-visit)
276 +(js2--struct-put 'rjsx-spread 'js2-printer 'rjsx-spread-print)
277 +
278 +(defun rjsx-spread-visit (ast callback)
279 + "Visit the `rjsx-spread' children of AST, invoking CALLBACK on them."
280 + (js2-visit-ast (rjsx-spread-expr ast) callback))
281 +
282 +(defun rjsx-spread-print (node indent-level)
283 + "Print the `rjsx-spread' NODE at INDENT-LEVEL."
284 + (insert (js2-make-pad indent-level) "{...")
285 + (js2-print-ast (rjsx-spread-expr node) 0)
286 + (insert "}"))
287 +
288 +(cl-defstruct (rjsx-wrapped-expr
289 + (:include js2-node (type rjsx-JSX-TEXT))
290 + (:constructor nil)
291 + (:constructor make-rjsx-wrapped-expr (&key pos len child)))
292 + child)
293 +
294 +(js2--struct-put 'rjsx-wrapped-expr 'js2-visitor 'rjsx-wrapped-expr-visit)
295 +(js2--struct-put 'rjsx-wrapped-expr 'js2-printer 'rjsx-wrapped-expr-print)
296 +
297 +(defun rjsx-wrapped-expr-visit (ast callback)
298 + "Visit the `rjsx-wrapped-expr' child of AST, invoking CALLBACK on them."
299 + (js2-visit-ast (rjsx-wrapped-expr-child ast) callback))
300 +
301 +(defun rjsx-wrapped-expr-print (node indent-level)
302 + "Print the `rjsx-wrapped-expr' NODE at INDENT-LEVEL."
303 + (insert (js2-make-pad indent-level) "{")
304 + (js2-print-ast (rjsx-wrapped-expr-child node) indent-level)
305 + (insert "}"))
306 +
307 +(cl-defstruct (rjsx-text
308 + (:include js2-node (type rjsx-JSX-TEXT))
309 + (:constructor nil)
310 + (:constructor make-rjsx-text (&key (pos (js2-current-token-beg))
311 + (len (js2-current-token-len))
312 + value)))
313 + value) ; a string
314 +
315 +(js2--struct-put 'rjsx-text 'js2-visitor 'js2-visit-none)
316 +(js2--struct-put 'rjsx-text 'js2-printer 'rjsx-text-print)
317 +
318 +(defun rjsx-text-print (node _indent-level)
319 + "Print the `rjsx-text' NODE at INDENT-LEVEL."
320 + ;; Text nodes include whitespace
321 + (insert (rjsx-text-value node)))
322 +
323 +
324 +;;;; Recursive descent parsing
325 +(defvar rjsx-print-debug-message nil "If t will print out debug messages.")
326 +;(setq rjsx-print-debug-message t)
327 +(defmacro rjsx-maybe-message (&rest args)
328 + "If debug is enabled, call `message' with ARGS."
329 + `(when rjsx-print-debug-message
330 + (message ,@args)))
331 +
332 +
333 +(js2-deflocal rjsx-in-xml nil "Variable used to track which xml parsing function is the outermost one.")
334 +
335 +(defun rjsx-parse-top-xml ()
336 + "Parse a top level XML fragment.
337 +This is the entry point when ‘js2-parse-unary-expr’ finds a '<' character"
338 + (rjsx-maybe-message "Parsing a new xml fragment%s" (if rjsx-in-xml ", recursively" ""))
339 + ;; If there are imbalanced tags, we just need to bail out to the
340 + ;; topmost JSX parser and let js2 handle the EOF. Our custom scanner
341 + ;; will throw `t' if it finds the EOF, which it ordinarily wouldn't
342 + (let (pn)
343 + (when (catch 'rjsx-eof-while-parsing
344 + (let ((rjsx-in-xml t)) ;; We use dynamic scope to handle xml > expr > xml nestings
345 + (setq pn (rjsx-parse-xml)))
346 + nil)
347 + (rjsx-maybe-message "Caught a signal. Rethrowing?: `%s'" rjsx-in-xml)
348 + (if rjsx-in-xml
349 + (throw 'rjsx-eof-while-parsing t)
350 + ;; We subtract 1 since js2 sets the cursor the the point after point-max
351 + (setq pn (make-js2-error-node :len (1- (js2-current-token-len))))
352 + (js2-report-error "msg.syntax" nil (js2-node-pos pn) (js2-node-len pn))))
353 + (rjsx-maybe-message "Returning from top xml function: %s" pn)
354 + pn))
355 +
356 +(defun rjsx-parse-xml ()
357 + "Parse a complete xml node from start to end tag."
358 + (let ((pn (make-rjsx-node)) self-closing name-n name-str child child-name-str)
359 + (rjsx-maybe-message "Starting rjsx-parse-xml after <")
360 + (if (setq child (rjsx-parse-empty-tag))
361 + child
362 + (setf (rjsx-node-name pn) (setq name-n (rjsx-parse-member-or-ns 'rjsx-tag)))
363 + (if (js2-error-node-p name-n)
364 + (progn (rjsx-maybe-message "could not parse tag name")
365 + (make-js2-error-node :pos (js2-node-pos pn) :len (1+ (js2-node-len name-n))))
366 + (js2-node-add-children pn name-n)
367 + (setq name-str (if (rjsx-member-p name-n) (rjsx-member-full-name name-n)
368 + (rjsx-identifier-full-name name-n)))
369 + (if js2-highlight-external-variables
370 + (let ((name-node (rjsx-identifier-name
371 + (if (rjsx-member-p name-n)
372 + (car (rjsx-member-idents name-n))
373 + name-n)))
374 + (case-fold-search nil))
375 + (when (string-match-p "^[[:upper:]]" (js2-name-node-name name-node))
376 + (js2-record-name-node name-node))))
377 + (rjsx-maybe-message "cleared tag name: '%s'" name-str)
378 + ;; Now parse the attributes
379 + (rjsx-parse-attributes pn)
380 + (rjsx-maybe-message "cleared attributes")
381 + ;; Now parse either a self closing tag or the end of the opening tag
382 + (rjsx-maybe-message "next type: `%s'" (js2-peek-token))
383 + (if (setq self-closing (js2-match-token js2-DIV))
384 + (progn
385 + (js2-record-text-property (js2-current-token-beg) (js2-current-token-end)
386 + 'rjsx-class 'self-closing-slash)
387 + ;; TODO: How do we un-mark old slashes?
388 + (js2-must-match js2-GT "msg.no.gt.after.slash"
389 + (js2-node-pos pn) (- (js2-current-token-end) (js2-node-pos pn))))
390 + (js2-must-match js2-GT "msg.no.gt.in.opener" (js2-node-pos pn) (js2-node-len pn)))
391 + (rjsx-maybe-message "cleared opener closer, self-closing: %s" self-closing)
392 + (if self-closing
393 + (setf (js2-node-len pn) (- (js2-current-token-end) (js2-node-pos pn)))
394 + (while (not (rjsx-closing-tag-p (setq child (rjsx-parse-child))))
395 + ;; rjsx-parse-child calls our scanner, which always moves
396 + ;; forward at least one character. If it hits EOF, it
397 + ;; signals to our caller, so we don't have to worry about infinite loops here
398 + (rjsx-maybe-message "parsed child")
399 + (rjsx-node-push-child pn child)
400 + (if (= 0 (js2-node-len child)) ; TODO: Does this ever happen?
401 + (js2-get-token)))
402 + (setq child-name-str (rjsx-closing-tag-full-name child))
403 + (unless (string= name-str child-name-str)
404 + (js2-report-error "msg.mismatched.close.tag" name-str (js2-node-pos child) (js2-node-len child)))
405 + (rjsx-maybe-message "cleared children for `%s'" name-str)
406 + (js2-node-add-children pn child)
407 + (setf (rjsx-node-closing-tag pn) child))
408 + (rjsx-maybe-message "Returning completed XML node")
409 + (setf (js2-node-len pn) (- (js2-current-token-end) (js2-node-pos pn)))
410 + pn))))
411 +
412 +(defun rjsx-parse-empty-tag ()
413 + "Check if we are in an empty tag of the form `</>' and consume it if so.
414 +Returns a `js2-error-node' if we are in one or nil if not."
415 + (let ((beg (js2-current-token-beg)))
416 + (when (js2-match-token js2-DIV)
417 + (if (js2-match-token js2-GT)
418 + (progn ; We're in a </> block, likely created by us in `rjsx-electric-lt'
419 + ;; We only highlight the < to reduce the visual impact
420 + (js2-report-error "msg.syntax" nil beg 1)
421 + (make-js2-error-node :pos beg :len (- (js2-current-token-end) beg)))
422 + ;; TODO: This is probably an unmatched closing tag. We should
423 + ;; consume it, mark it an error, and move on
424 + (js2-unget-token)
425 + nil))))
426 +
427 +(defun rjsx-parse-attributes (parent)
428 + "Parse all attributes, including key=value and {...spread}, and add them to PARENT."
429 + ;; Getting this function to not hang in the loop proved tricky. The
430 + ;; key is that `rjsx-parse-spread' and `rjsx-parse-single-attr' both
431 + ;; return `js2-error-node's if they fail to consume any tokens,
432 + ;; which signals to us that we just need to discard one token and
433 + ;; keep going.
434 + (let (attr
435 + (loop-terminators (list js2-DIV js2-GT js2-EOF js2-ERROR)))
436 + (while (not (memql (js2-peek-token) loop-terminators))
437 + (rjsx-maybe-message "Starting loop. Next token type: %s\nToken pos: %s" (js2-peek-token) (js2-current-token-beg))
438 + (setq attr
439 + (if (js2-match-token js2-LC)
440 + (or (rjsx-check-for-empty-curlies t)
441 + (prog1 (rjsx-parse-spread)
442 + (rjsx-maybe-message "Parsed spread")))
443 + (rjsx-maybe-message "Parsing single attr")
444 + (rjsx-parse-single-attr)))
445 + (when (js2-error-node-p attr) (js2-get-token))
446 + ; TODO: We should make this conditional on
447 + ; `js2-recover-from-parse-errors'
448 + (rjsx-node-push-prop parent attr))))
449 +
450 +
451 +(cl-defun rjsx-check-for-empty-curlies (&optional dont-consume-rc &key check-for-comments warning)
452 + "If the following token is '}' set empty curly errors.
453 +If DONT-CONSUME-RC is non-nil, the matched right curly token
454 +won't be consumed. Returns a `js2-error-node' if the curlies are
455 +empty or nil otherwise. If CHECK-FOR-COMMENTS (a &KEY argument)
456 +is non-nil, this will check for comments inside the curlies and
457 +returns a `js2-empty-expr-node' if any are found. If WARNING (a
458 +&key argument) is non-nil, reports the empty curlies as a warning
459 +and not an error and also returns a `js2-empty-expr-node'.
460 +Assumes the current token is a '{'."
461 + (let ((beg (js2-current-token-beg)) end len)
462 + (when (js2-match-token js2-RC)
463 + (setq end (js2-current-token-end))
464 + (setq len (- end beg))
465 + (when dont-consume-rc
466 + (js2-unget-token))
467 + (if check-for-comments (rjsx-maybe-message "Checking for comments between %d and %d" beg end))
468 + (unless (and check-for-comments
469 + (dolist (comment js2-scanned-comments)
470 + (rjsx-maybe-message "Comment at %d, length=%d"
471 + (js2-node-pos comment)
472 + (js2-node-len comment))
473 + ;; TODO: IF comments are in reverse document order, we should be able to
474 + ;; bail out early and know we didn't find one
475 + (when (and (>= (js2-node-pos comment) beg)
476 + (<= (+ (js2-node-pos comment) (js2-node-len comment)) end))
477 + (cl-return-from rjsx-check-for-empty-curlies
478 + (make-js2-empty-expr-node :pos beg :len (- end beg))))))
479 + (if warning
480 + (progn (js2-report-warning "msg.empty.expr" nil beg len)
481 + (make-js2-empty-expr-node :pos beg :len (- end beg)))
482 + (js2-report-error "msg.empty.expr" nil beg len)
483 + (make-js2-error-node :pos beg :len len))))))
484 +
485 +
486 +(defun rjsx-parse-spread ()
487 + "Parse an {...props} attribute."
488 + (let ((pn (make-rjsx-spread :pos (js2-current-token-beg)))
489 + (beg (js2-current-token-beg))
490 + missing-dots expr)
491 + (setq missing-dots (not (js2-match-token js2-TRIPLEDOT)))
492 + ;; parse-assign-expr will go crazy if we're looking at `} /', so we
493 + ;; check for an empty spread first
494 + (if (js2-match-token js2-RC)
495 + (setq expr (make-js2-error-node :len 1))
496 + (setq expr (js2-parse-assign-expr))
497 + (when (js2-error-node-p expr)
498 + (pop js2-parsed-errors))) ; We'll add our own error
499 + (unless (or (js2-match-token js2-RC) (js2-error-node-p expr))
500 + (js2-report-error "msg.no.rc.after.spread" nil
501 + beg (- (js2-current-token-end) beg)))
502 + (setf (rjsx-spread-expr pn) expr)
503 + (setf (js2-node-len pn) (- (js2-current-token-end) (js2-node-pos pn)))
504 + (js2-node-add-children pn expr)
505 + (if (js2-error-node-p expr)
506 + (js2-report-error "msg.syntax" nil beg (- (js2-current-token-end) beg))
507 + (when missing-dots
508 + (js2-report-error "msg.no.dots.in.prop.spread" nil beg (js2-node-len pn))))
509 + (if (= 0 (js2-node-len pn)) ; TODO: Is this ever possible?
510 + (make-js2-error-node :pos beg :len 0)
511 + pn)))
512 +
513 +(defun rjsx-parse-single-attr ()
514 + "Parse an 'a=b' JSX attribute and return the corresponding XML node."
515 + (let ((pn (make-rjsx-attr)) name value beg)
516 + (setq name (rjsx-parse-identifier 'rjsx-attr)) ; Won't consume token on error
517 + (if (js2-error-node-p name)
518 + name
519 + (setf (rjsx-attr-name pn) name)
520 + (setq beg (js2-node-pos name))
521 + (js2-node-add-children pn name)
522 + (rjsx-maybe-message "Got the name for the attr: `%s'" (rjsx-identifier-full-name name))
523 + (if (js2-match-token js2-ASSIGN) ; Won't consume on error
524 + (progn
525 + (rjsx-maybe-message "Matched the equals sign")
526 + (if (js2-match-token js2-LC)
527 + (setq value (rjsx-parse-wrapped-expr nil t))
528 + (if (js2-match-token js2-STRING)
529 + (setq value (rjsx-parse-string))
530 + (js2-report-error "msg.no.value.after.jsx.prop" (rjsx-identifier-full-name name)
531 + beg (- (js2-current-token-end) beg))
532 + (setq value (make-js2-error-node :pos beg :len (js2-current-token-len))))))
533 + (setq value (make-js2-empty-expr-node :pos (js2-current-token-end) :len 0)))
534 + (rjsx-maybe-message "value type: `%s'" (js2-node-type value))
535 + (setf (rjsx-attr-value pn) value)
536 + (setf (js2-node-len pn) (- (js2-node-end value) (js2-node-pos pn)))
537 + (js2-node-add-children pn value)
538 + (rjsx-maybe-message "Finished single attribute.")
539 + pn)))
540 +
541 +(defun rjsx-parse-wrapped-expr (allow-empty skip-to-rc)
542 + "Parse a curly-brace-wrapped JS expression.
543 +If ALLOW-EMPTY is non-nil, will warn for empty braces, otherwise
544 +will signal a syntax error. If it does not find a right curly
545 +and SKIP-TO-RC is non-nil, after the expression, consumes tokens
546 +until the end of the JSX node"
547 + (rjsx-maybe-message "parsing wrapped expression")
548 + (let (pn
549 + (beg (js2-current-token-beg))
550 + (child (rjsx-check-for-empty-curlies nil
551 + :check-for-comments allow-empty
552 + :warning allow-empty)))
553 + (if child
554 + (if allow-empty
555 + (make-rjsx-wrapped-expr :pos beg :len (js2-node-len child) :child child)
556 + child) ;; Will be an error node in this case
557 + (setq child (js2-parse-assign-expr))
558 + (rjsx-maybe-message "parsed expression, type: `%s'" (js2-node-type child))
559 + (setq pn (make-rjsx-wrapped-expr :pos beg :child child))
560 + (js2-node-add-children pn child)
561 + (when (js2-error-node-p child)
562 + (pop js2-parsed-errors)) ; We'll record our own message after checking for RC
563 + (if (js2-match-token js2-RC)
564 + (rjsx-maybe-message "matched } after expression")
565 + (rjsx-maybe-message "did not match } after expression")
566 + (when skip-to-rc
567 + (while (not (memql (js2-get-token) (list js2-RC js2-EOF js2-DIV js2-GT)))
568 + (rjsx-maybe-message "Skipped over `%s'" (js2-current-token-string)))
569 + (when (memq (js2-current-token-type) (list js2-DIV js2-GT))
570 + (js2-unget-token)))
571 + (unless (js2-error-node-p child)
572 + (js2-report-error "msg.no.rc.after.expr" nil beg
573 + (- (js2-current-token-beg) beg))))
574 + (when (js2-error-node-p child)
575 + (js2-report-error "msg.syntax" nil beg (- (js2-current-token-end) beg)))
576 + (setf (js2-node-len pn) (- (js2-current-token-end) beg))
577 + pn)))
578 +
579 +(defun rjsx-parse-string ()
580 + "Verify that current token is a valid JSX string.
581 +Returns a `js2-error-node' if TOKEN-STRING is not a valid JSX
582 +string, otherwise returns a `js2-string-node'. (Strings are
583 +invalid if they contain the delimiting quote character inside)"
584 + (rjsx-maybe-message "Parsing string")
585 + (let* ((token (js2-current-token))
586 + (beg (js2-token-beg token))
587 + (len (- (js2-token-end token) beg))
588 + (token-string (js2-token-string token)) ;; JS2 does not include the quote-chars
589 + (quote-char (char-before (js2-token-end token))))
590 + (if (cl-position quote-char token-string)
591 + (progn
592 + (js2-report-error "msg.invalid.jsx.string" nil beg len)
593 + (make-js2-error-node :pos beg :len len))
594 + (make-js2-string-node :pos beg :len len :value token-string))))
595 +
596 +(cl-defun rjsx-parse-identifier (&optional face &key (allow-ns t))
597 + "Parse a possibly namespaced identifier and fontify with FACE if given.
598 +Returns a `js2-error-node' if unable to parse. If the &key
599 +argument ALLOW-NS is nil, does not allow namespaced names."
600 + (if (js2-must-match-name "msg.bad.jsx.ident")
601 + (let ((pn (make-rjsx-identifier))
602 + (beg (js2-current-token-beg))
603 + (name-parts (list (js2-current-token-string)))
604 + (allow-colon allow-ns)
605 + (continue t)
606 + (prev-token-end (js2-current-token-end))
607 + (name-start (js2-current-token-beg))
608 + matched-colon)
609 + (while (and continue
610 + (or (and (memq (js2-peek-token) (list js2-SUB js2-ASSIGN_SUB))
611 + (prog2 ; Ensure no whitespace between previous name and this dash
612 + (js2-get-token)
613 + (eq prev-token-end (js2-current-token-beg))
614 + (js2-unget-token)))
615 + (and allow-colon (= (js2-peek-token) js2-COLON))))
616 + (if (setq matched-colon (js2-match-token js2-COLON))
617 + (setf (rjsx-identifier-namespace pn) (apply #'concat (nreverse name-parts))
618 + allow-colon nil
619 + name-parts (list)
620 + name-start nil)
621 + (when (= (js2-get-token) js2-ASSIGN_SUB) ; Otherwise it's a js2-SUB
622 + (setf (js2-token-end (js2-current-token)) (1- (js2-current-token-end))
623 + (js2-token-type (js2-current-token)) js2-SUB
624 + (js2-token-string (js2-current-token)) "-"
625 + js2-ts-cursor (1+ (js2-current-token-beg))
626 + js2-ti-lookahead 0))
627 + (push "-" name-parts))
628 + (setq prev-token-end (js2-current-token-end))
629 + (if (js2-match-token js2-NAME)
630 + (if (eq prev-token-end (js2-current-token-beg))
631 + (progn (push (js2-current-token-string) name-parts)
632 + (setq prev-token-end (js2-current-token-end)
633 + name-start (or name-start (js2-current-token-beg))))
634 + (js2-unget-token)
635 + (setq continue nil))
636 + (when (= js2-COLON (js2-current-token-type))
637 + (js2-report-error "msg.bad.jsx.ident" nil beg (- (js2-current-token-end) beg)))
638 + ;; We only keep going if this is an `ident-ending-with-dash-colon:'
639 + (setq continue (and (not matched-colon) (= (js2-peek-token) js2-COLON)))))
640 + (when face
641 + (js2-set-face beg (js2-current-token-end) face 'record))
642 + (setf (js2-node-len pn) (- (js2-current-token-end) beg)
643 + (rjsx-identifier-name pn) (if name-start
644 + (make-js2-name-node :pos name-start
645 + :len (- (js2-current-token-end) name-start)
646 + :name (apply #'concat (nreverse name-parts)))
647 + (make-js2-name-node :pos (js2-current-token-end) :len 0 :name "")))
648 + pn)
649 + (make-js2-error-node :len (js2-current-token-len))))
650 +
651 +(defun rjsx-parse-member-or-ns (&optional face)
652 + "Parse a dotted expression or a namespaced identifier and fontify with FACE if given."
653 + (let ((ident (rjsx-parse-identifier face)))
654 + (cond
655 + ((js2-error-node-p ident) ident)
656 + ((rjsx-identifier-namespace ident) ident)
657 + (t (rjsx-parse-member ident face)))))
658 +
659 +(defun rjsx-parse-member (ident &optional face)
660 + "Parse a dotted member expression starting with IDENT and fontify with FACE.
661 +IDENT is the `rjsx-identifier' node for the first item in the
662 +member expression. Returns a `js2-error-node' if unable to
663 +parse."
664 + (let (idents dots-pos pn end)
665 + (setq pn (make-rjsx-member :pos (js2-node-pos ident)))
666 + (setq end (js2-current-token-end))
667 + (push ident idents)
668 + (while (and (js2-match-token js2-DOT) (not (js2-error-node-p ident)))
669 + (push (js2-current-token-beg) dots-pos)
670 + (setq end (js2-current-token-end))
671 + (setq ident (rjsx-parse-identifier nil :allow-ns nil))
672 + (push ident idents)
673 + (unless (js2-error-node-p ident)
674 + (setq end (js2-current-token-end)))
675 + (js2-node-add-children pn ident))
676 + (setf (rjsx-member-idents pn) (nreverse idents)
677 + (rjsx-member-dots-pos pn) (nreverse dots-pos)
678 + (js2-node-len pn) (- end (js2-node-pos pn)))
679 + (when face
680 + (js2-set-face (js2-node-pos pn) end face 'record))
681 + pn))
682 +
683 +
684 +(defun rjsx-parse-child ()
685 + "Parse an XML child node.
686 +Child nodes include plain (unquoted) text, other XML elements,
687 +and {}-bracketed expressions. Return the parsed child."
688 + (let ((tt (rjsx-get-next-xml-token)))
689 + (rjsx-maybe-message "child type `%s'" tt)
690 + (cond
691 + ((= tt js2-LT)
692 + (rjsx-maybe-message "xml-or-close")
693 + (rjsx-parse-xml-or-closing-tag))
694 +
695 + ((= tt js2-LC)
696 + (rjsx-maybe-message "parsing expression { %s" (js2-peek-token))
697 + (rjsx-parse-wrapped-expr t nil))
698 +
699 + ((= tt rjsx-JSX-TEXT)
700 + (rjsx-maybe-message "text node: '%s'" (js2-current-token-string))
701 + (js2-set-face (js2-current-token-beg) (js2-current-token-end) 'rjsx-text 'record)
702 + (js2-record-text-property (js2-current-token-beg) (js2-current-token-end)
703 + 'syntax-table (standard-syntax-table))
704 + (make-rjsx-text :value (js2-current-token-string)))
705 +
706 + ((= tt js2-ERROR)
707 + (make-js2-error-node :len (js2-current-token-len)))
708 +
709 + (t (error "Unexpected token type: %s" (js2-peek-token))))))
710 +
711 +(defun rjsx-parse-xml-or-closing-tag ()
712 + "Parse a JSX tag, which could be a child or a closing tag.
713 +Return the parsed child, which is a `rjsx-closing-tag' if a
714 +closing tag was parsed."
715 + (let ((beg (js2-current-token-beg)) pn)
716 + (if (setq pn (rjsx-parse-empty-tag))
717 + pn
718 + (if (js2-match-token js2-DIV)
719 + (progn (setq pn (make-rjsx-closing-tag :pos beg :name (rjsx-parse-member-or-ns 'rjsx-tag)))
720 + (if (js2-must-match js2-GT "msg.no.gt.in.closer" beg (- (js2-current-token-end) beg))
721 + (rjsx-maybe-message "parsed closing tag")
722 + (rjsx-maybe-message "missing closing `>'"))
723 + (setf (js2-node-len pn) (- (js2-current-token-end) beg))
724 + pn)
725 + (rjsx-maybe-message "parsing a child XML item")
726 + (rjsx-parse-xml)))))
727 +
728 +(defun rjsx-get-next-xml-token ()
729 + "Scan through the XML text and push one token onto the stack."
730 + (setq js2-ts-string-buffer nil) ; for recording the text
731 + (when (> js2-ti-lookahead 0)
732 + (setq js2-ts-cursor (js2-current-token-end))
733 + (setq js2-ti-lookahead 0))
734 +
735 + (let ((token (js2-new-token 0))
736 + c)
737 + (rjsx-maybe-message "Running the xml scanner")
738 + (catch 'return
739 + (while t
740 + (setq c (js2-get-char))
741 + (rjsx-maybe-message "'%s' (%s)" (if (= c js2-EOF_CHAR) "EOF" (char-to-string c)) c)
742 + (cond
743 + ((or (= c ?}) (= c ?>))
744 + (js2-set-string-from-buffer token)
745 + (setf (js2-token-type token) js2-ERROR)
746 + (js2-report-scan-error "msg.syntax" t)
747 + (throw 'return js2-ERROR))
748 +
749 + ((or (= c ?<) (= c ?{))
750 + (js2-unget-char)
751 + (if js2-ts-string-buffer
752 + (progn
753 + (js2-set-string-from-buffer token)
754 + (setf (js2-token-type token) rjsx-JSX-TEXT)
755 + (rjsx-maybe-message "created rjsx-JSX-TEXT token: `%s'" (js2-token-string token))
756 + (throw 'return rjsx-JSX-TEXT))
757 + (js2-get-char)
758 + (js2-set-string-from-buffer token)
759 + (setf (js2-token-type token) (if (= c ?<) js2-LT js2-LC))
760 + (setf (js2-token-string token) (string c))
761 + (throw 'return (js2-token-type token))))
762 +
763 + ((= c js2-EOF_CHAR)
764 + (js2-set-string-from-buffer token)
765 + (rjsx-maybe-message "Hit EOF. Current buffer: `%s'" (js2-token-string token))
766 + (setf (js2-token-type token) js2-ERROR)
767 + (rjsx-maybe-message "Scanner hit EOF. Panic!")
768 + (throw 'rjsx-eof-while-parsing t))
769 + (t (js2-add-to-string c)))))))
770 +
771 +(defun rjsx--tag-at-point ()
772 + "Return the JSX tag at point, if any, or nil."
773 + (let ((node (js2-node-at-point (point) t)))
774 + (while (and node (not (rjsx-node-p node)))
775 + (setq node (js2-node-parent node)))
776 + node))
777 +
778 +
779 +;;;; Interactive commands and keybindings
780 +(defun rjsx-electric-lt (n)
781 + "Insert a context-sensitive less-than sign.
782 +Optional prefix argument N indicates how many signs to insert.
783 +If N is greater than one, no special handling takes place.
784 +Otherwise, if the less-than sign would start a JSX block, it
785 +inserts `< />' and places the cursor inside the new tag."
786 + (interactive "p")
787 + (if (/= n 1)
788 + (insert (make-string n "<"))
789 + (let ((inhibit-changing-match-data t))
790 + (if (looking-back (rx (or "=" "(" "?" ":" ">" "}" "&" "|" "{" ","
791 + "return")
792 + (zero-or-more (or "\n" space)))
793 + (point-at-bol -2))
794 + (progn (insert "</>")
795 + (backward-char 2))
796 + (insert "<")))))
797 +
798 +(define-key rjsx-mode-map "<" 'rjsx-electric-lt)
799 +
800 +(defun rjsx-delete-creates-full-tag (n &optional killflag)
801 + "N and KILLFLAG are as in `delete-char'.
802 +If N is 1 and KILLFLAG nil, checks to see if we're in a
803 +self-closing tag about to delete the slash. If so, deletes the
804 +slash and inserts a matching end-tag."
805 + (interactive "p")
806 + (if (or killflag (/= 1 n) (not (eq (get-char-property (point) 'rjsx-class) 'self-closing-slash)))
807 + (if (called-interactively-p 'any)
808 + (call-interactively 'delete-forward-char)
809 + (delete-char n killflag))
810 + (let ((node (rjsx--tag-at-point)))
811 + (if node
812 + (progn
813 + (delete-char 1)
814 + (search-forward ">" )
815 + (save-excursion
816 + (insert "</" (rjsx-node-opening-tag-name node) ">")))
817 + (delete-char 1)))))
818 +
819 +(define-key rjsx-mode-map (kbd "C-d") 'rjsx-delete-creates-full-tag)
820 +
821 +(defun rjsx-rename-tag-at-point (new-name)
822 + "Prompt for a new name and modify the tag at point.
823 +NEW-NAME is the name to give the tag."
824 + (interactive "sNew tag name: ")
825 + (let ((tag (rjsx--tag-at-point)) closer)
826 + (if tag
827 + (let* ((head (rjsx-node-name tag))
828 + (tail (when (setq closer (rjsx-node-closing-tag tag)) (rjsx-closing-tag-name closer)))
829 + beg end)
830 + (dolist (part (if tail (list tail head) (list head)))
831 + (setq beg (js2-node-abs-pos part)
832 + end (+ beg (js2-node-len part)))
833 + (delete-region beg end)
834 + (save-excursion (goto-char beg) (insert new-name)))
835 + (js2-reparse))
836 + (message "No JSX tag found at point"))))
837 +
838 +(define-key rjsx-mode-map (kbd "C-c C-r") 'rjsx-rename-tag-at-point)
839 +
840 +
841 +(provide 'rjsx-mode)
842 +;;; rjsx-mode.el ends here
843 +
844 +;; Local Variables:
845 +;; outline-regexp: ";;;\\(;* [^
846 +;; ]\\|###autoload\\)\\|(....."
847 +;; End:
No preview for this file type