]> git.eshelyaron.com Git - emacs.git/commitdiff
Automatically detect JSX in JavaScript files
authorJackson Ray Hamilton <jackson@jacksonrayhamilton.com>
Sun, 24 Mar 2019 03:14:29 +0000 (20:14 -0700)
committerJackson Ray Hamilton <jackson@jacksonrayhamilton.com>
Tue, 9 Apr 2019 05:48:22 +0000 (22:48 -0700)
* 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”).

lisp/files.el
lisp/progmodes/js.el

index 1dae57593a0d669e0ec445bb0e467411053fac3b..b81550e297ca53443d492292f3dbe0765e1a4f1c 100644 (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)
index df2c41332e718a23b03442bdc8b15c7da9b42966..0bba8159c18bb8d1054f9e51958e34d65e370551 100644 (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
+Facebook’s “JSX” syntax extension for JavaScript, for use with
+Facebook’s “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)