Automatically detect JSX in JavaScript files

* lisp/files.el (auto-mode-alist): Simply enable
javascript-mode (js-mode) when opening “.jsx” files, since the “.jsx”
file extension will be used as an indicator of JSX syntax by js-mode,
and more code is likely to work in js-mode than js-jsx-mode, and we
probably want to guide users to use js-mode (with js-jsx-syntax)
instead.  Code that used to work exclusively in js-jsx-mode (if anyone
ever wrote any) ought to be updated to work in js-mode too when
js-jsx-syntax is set to t.

* lisp/progmodes/js.el (js-jsx-detect-syntax, js-jsx-regexps)
(js-jsx--detect-and-enable, js-jsx--detect-after-change): New
variables and functions for detecting and enabling JSX.

(js-jsx-syntax): Update docstring with respect to the widened scope of
the effects and use of this variable.

(js-syntactic-mode-name, js--update-mode-name)
(js--idly-update-mode-name, js-jsx-enable): New variable and functions
for indicating when JSX is enabled.

(js-mode): Detect and enable JSX.  Print all enabled syntaxes after
the mode name whenever Emacs goes idle; this ensures lately-enabled
syntaxes are evident.

(js-jsx-mode): Update mode name for consistency with the state in
which JSX is enabled in js-mode.  Update docstring to suggest
alternative means of using JSX without this mode.  Going forward, it
may be best to gently guide users away from js-jsx-mode, since a “one
mode per syntax extension” model would not scale well if more syntax
extensions were to be simultaneously supported (e.g. Facebook’s
“Flow”).
This commit is contained in:
Jackson Ray Hamilton 2019-03-23 20:14:29 -07:00
parent 339be7c007
commit bf37078df2
No known key found for this signature in database
GPG key ID: B4771664B476B290
2 changed files with 115 additions and 7 deletions

View file

@ -2705,9 +2705,8 @@ ARC\\|ZIP\\|LZH\\|LHA\\|ZOO\\|[JEW]AR\\|XPI\\|RAR\\|CBR\\|7Z\\)\\'" . archive-mo
("\\.dbk\\'" . xml-mode)
("\\.dtd\\'" . sgml-mode)
("\\.ds\\(ss\\)?l\\'" . dsssl-mode)
("\\.jsm?\\'" . javascript-mode)
("\\.js[mx]?\\'" . javascript-mode)
("\\.json\\'" . javascript-mode)
("\\.jsx\\'" . js-jsx-mode)
("\\.[ds]?vh?\\'" . verilog-mode)
("\\.by\\'" . bovine-grammar-mode)
("\\.wy\\'" . wisent-grammar-mode)

View file

@ -574,10 +574,30 @@ then the \".\"s will be lined up:
:safe 'booleanp
:group 'js)
(defcustom js-jsx-detect-syntax t
"When non-nil, automatically detect whether JavaScript uses JSX.
`js-jsx-syntax' (which see) may be made buffer-local and set to
t. The detection strategy can be customized by adding elements
to `js-jsx-regexps', which see."
:version "27.1"
:type 'boolean
:safe 'booleanp
:group 'js)
(defcustom js-jsx-syntax nil
"When non-nil, parse JavaScript with consideration for JSX syntax.
This fixes indentation of JSX code in some cases. It is set to
be buffer-local when in `js-jsx-mode'."
This enables proper font-locking and indentation of code using
Facebooks JSX syntax extension for JavaScript, for use with
Facebooks React library. Font-locking is like sgml-mode.
Indentation is also like sgml-mode, although some indentation
behavior may differ slightly to align more closely with the
conventions of the React developer community.
When `js-mode' is already enabled, you should call
`js-jsx-enable' to set this variable.
It is set to be buffer-local (and t) when in `js-jsx-mode'."
:version "27.1"
:type 'boolean
:safe 'booleanp
@ -4223,6 +4243,79 @@ If one hasn't been set, or if it's stale, prompt for a new one."
(when temp-name
(delete-file temp-name))))))
;;; Syntax extensions
(defvar js-syntactic-mode-name t
"If non-nil, print enabled syntaxes in the mode name.")
(defun js--update-mode-name ()
"Print enabled syntaxes if `js-syntactic-mode-name' is t."
(when js-syntactic-mode-name
(setq mode-name (concat "JavaScript"
(if js-jsx-syntax "+JSX" "")))))
(defun js--idly-update-mode-name ()
"Update `mode-name' whenever Emacs goes idle.
In case `js-jsx-syntax' is updated, especially by features of
Emacs like .dir-locals.el or file variables, this ensures the
modeline eventually reflects which syntaxes are enabled."
(let (timer)
(setq timer
(run-with-idle-timer
0 t
(lambda (buffer)
(if (buffer-live-p buffer)
(with-current-buffer buffer
(js--update-mode-name))
(cancel-timer timer)))
(current-buffer)))))
(defun js-jsx-enable ()
"Enable JSX in the current buffer."
(interactive)
(setq-local js-jsx-syntax t)
(js--update-mode-name))
(defvar js-jsx-regexps
(list "\\_<\\(?:var\\|let\\|const\\|import\\)\\_>.*?React")
"Regexps for detecting JSX in JavaScript buffers.
When `js-jsx-detect-syntax' is non-nil and any of these regexps
match text near the beginning of a JavaScript buffer,
`js-jsx-syntax' (which see) will be made buffer-local and set to
t.")
(defun js-jsx--detect-and-enable (&optional arbitrarily)
"Detect if JSX is likely to be used, and enable it if so.
Might make `js-jsx-syntax' buffer-local and set it to t. Matches
from the beginning of the buffer, unless optional arg ARBITRARILY
is non-nil. Return t after enabling, nil otherwise."
(when (or (and (buffer-file-name)
(string-match-p "\\.jsx\\'" (buffer-file-name)))
(and js-jsx-detect-syntax
(save-excursion
(unless arbitrarily
(goto-char (point-min)))
(catch 'match
(mapc
(lambda (regexp)
(if (re-search-forward regexp 4000 t) (throw 'match t)))
js-jsx-regexps)
nil))))
(js-jsx-enable)
t))
(defun js-jsx--detect-after-change (beg end _len)
"Detect if JSX is likely to be used after a change.
This function is intended for use in `after-change-functions'."
(when (<= end 4000)
(save-excursion
(goto-char beg)
(beginning-of-line)
(save-restriction
(narrow-to-region (point) end)
(when (js-jsx--detect-and-enable 'arbitrarily)
(remove-hook 'after-change-functions #'js-jsx--detect-after-change t))))))
;;; Main Function
;;;###autoload
@ -4259,6 +4352,12 @@ If one hasn't been set, or if it's stale, prompt for a new one."
;; Frameworks
(js--update-quick-match-re)
;; Syntax extensions
(unless (js-jsx--detect-and-enable)
(add-hook 'after-change-functions #'js-jsx--detect-after-change nil t))
(js--update-mode-name) ; If `js-jsx-syntax' was set from outside.
(js--idly-update-mode-name)
;; Imenu
(setq imenu-case-fold-search nil)
(setq imenu-create-index-function #'js--imenu-create-index)
@ -4304,10 +4403,20 @@ If one hasn't been set, or if it's stale, prompt for a new one."
)
;;;###autoload
(define-derived-mode js-jsx-mode js-mode "JSX"
"Major mode for editing JSX."
(define-derived-mode js-jsx-mode js-mode "JavaScript+JSX"
"Major mode for editing JavaScript+JSX.
Simply makes `js-jsx-syntax' buffer-local and sets it to t.
`js-mode' may detect and enable support for JSX automatically if
it appears to be used in a JavaScript file. You could also
customize `js-jsx-regexps' to improve that detection; or, you
could set `js-jsx-syntax' to t in your init file, or in a
.dir-locals.el file, or using file variables; or, you could call
`js-jsx-enable' in `js-mode-hook'. You may be better served by
one of the aforementioned options instead of using this mode."
:group 'js
(setq-local js-jsx-syntax t))
(js-jsx-enable))
;;;###autoload (defalias 'javascript-mode 'js-mode)