[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] master 667d320 034/110: Merge pull request #282 from jacksonrayha
From: |
Dmitry Gutov |
Subject: |
[elpa] master 667d320 034/110: Merge pull request #282 from jacksonrayhamilton/js2-jsx-mode |
Date: |
Thu, 23 Jun 2016 01:12:55 +0000 (UTC) |
branch: master
commit 667d320d3a3365a87ebb11a822ac19cd4133bdea
Merge: c63c700 1f95553
Author: Dmitry Gutov <address@hidden>
Commit: Dmitry Gutov <address@hidden>
Merge pull request #282 from jacksonrayhamilton/js2-jsx-mode
Add js2-jsx-mode
---
NEWS.md | 3 +
js2-mode.el | 21 ++++++
js2-old-indent.el | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/indent.el | 97 +++++++++++++++++++++++-
4 files changed, 330 insertions(+), 2 deletions(-)
diff --git a/NEWS.md b/NEWS.md
index 30500e0..33ba376 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -10,6 +10,9 @@
name will get more confusing.
* Support for default parameters in destructuring. It should work for both
objects and arrays, in both literals and function arguments.
+* New mode: `js2-jsx-mode`, deriving from `js2-mode`. Supports indentation of
+ JSXElement expressions wrapped within parentheses or as function arguments.
+ Indentation is customizable via `sgml-attribute-offset`.
## 20150909
diff --git a/js2-mode.el b/js2-mode.el
index 643e921..2c59a40 100644
--- a/js2-mode.el
+++ b/js2-mode.el
@@ -60,6 +60,12 @@
;; (add-to-list 'interpreter-mode-alist '("node" . js2-mode))
+;; Support for JSX is available via the derived mode `js2-jsx-mode'. If you
+;; also want JSX support, use that mode instead:
+
+;; (add-to-list 'auto-mode-alist '("\\.js\\'" . js2-jsx-mode))
+;; (add-to-list 'interpreter-mode-alist '("node" . js2-jsx-mode))
+
;; To customize how it works:
;; M-x customize-group RET js2-mode RET
@@ -95,6 +101,7 @@
(require 'js2-old-indent)
(defvaralias 'js2-basic-offset 'js-indent-level nil)
(defalias 'js2-proper-indentation 'js--proper-indentation)
+ (defalias 'js2-jsx-indent-line 'js-jsx-indent-line)
(defalias 'js2-indent-line 'js-indent-line)
(defalias 'js2-re-search-forward 'js--re-search-forward)))
@@ -11373,6 +11380,20 @@ Selecting an error will jump it to the corresponding
source-buffer error.
(js2-reparse))
+;; We may eventually want js2-jsx-mode to derive from js-jsx-mode, but that'd
be
+;; a bit more complicated and it doesn't net us much yet.
+;;;###autoload
+(define-derived-mode js2-jsx-mode js2-mode "JSX-IDE"
+ "Major mode for editing JSX code.
+
+To customize the indentation for this mode, set the SGML offset
+variables (`sgml-basic-offset' et al) locally, like so:
+
+ (defun set-jsx-indentation ()
+ (setq-local sgml-basic-offset js2-basic-offset))
+ (add-hook 'js2-jsx-mode-hook #'set-jsx-indentation)"
+ (set (make-local-variable 'indent-line-function) #'js2-jsx-indent-line))
+
(defun js2-mode-exit ()
"Exit `js2-mode' and clean up."
(interactive)
diff --git a/js2-old-indent.el b/js2-old-indent.el
index 64a874e..82a00b4 100644
--- a/js2-old-indent.el
+++ b/js2-old-indent.el
@@ -54,6 +54,8 @@
;;; Code:
+(require 'sgml-mode)
+
(defvar js2-language-version)
(declare-function js2-mark-safe-local "js2-mode")
@@ -493,6 +495,215 @@ indentation is aligned to that column."
(when (cl-plusp offset)
(forward-char offset)))))
+;;; JSX Indentation
+
+;; The following JSX indentation code is copied basically verbatim from js.el
at
+;; 958da7f, except that the prefixes on the functions/variables are changed.
+
+(defsubst js2--jsx-find-before-tag ()
+ "Find where JSX starts.
+
+Assume JSX appears in the following instances:
+- Inside parentheses, when returned or as the first argument
+ to a function, and after a newline
+- When assigned to variables or object properties, but only
+ on a single line
+- As the N+1th argument to a function
+
+This is an optimized version of (re-search-backward \"[(,]\n\"
+nil t), except set point to the end of the match. This logic
+executes up to the number of lines in the file, so it should be
+really fast to reduce that impact."
+ (let (pos)
+ (while (and (> (point) (point-min))
+ (not (progn
+ (end-of-line 0)
+ (when (or (eq (char-before) 40) ; (
+ (eq (char-before) 44)) ; ,
+ (setq pos (1- (point))))))))
+ pos))
+
+(defconst js2--jsx-end-tag-re
+ (concat "</" sgml-name-re ">\\|/>")
+ "Find the end of a JSX element.")
+
+(defconst js2--jsx-after-tag-re "[),]"
+ "Find where JSX ends.
+This complements the assumption of where JSX appears from
+`js--jsx-before-tag-re', which see.")
+
+(defun js2--jsx-indented-element-p ()
+ "Determine if/how the current line should be indented as JSX.
+
+Return `first' for the first JSXElement on its own line.
+Return `nth' for subsequent lines of the first JSXElement.
+Return `expression' for an embedded JS expression.
+Return `after' for anything after the last JSXElement.
+Return nil for non-JSX lines.
+
+Currently, JSX indentation supports the following styles:
+
+- Single-line elements (indented like normal JS):
+
+ var element = <div></div>;
+
+- Multi-line elements (enclosed in parentheses):
+
+ function () {
+ return (
+ <div>
+ <div></div>
+ </div>
+ );
+ }
+
+- Function arguments:
+
+ React.render(
+ <div></div>,
+ document.querySelector('.root')
+ );"
+ (let ((current-pos (point))
+ (current-line (line-number-at-pos))
+ last-pos
+ before-tag-pos before-tag-line
+ tag-start-pos tag-start-line
+ tag-end-pos tag-end-line
+ after-tag-line
+ parens paren type)
+ (save-excursion
+ (and
+ ;; Determine if we're inside a jsx element
+ (progn
+ (end-of-line)
+ (while (and (not tag-start-pos)
+ (setq last-pos (js2--jsx-find-before-tag)))
+ (while (forward-comment 1))
+ (when (= (char-after) 60) ; <
+ (setq before-tag-pos last-pos
+ tag-start-pos (point)))
+ (goto-char last-pos))
+ tag-start-pos)
+ (progn
+ (setq before-tag-line (line-number-at-pos before-tag-pos)
+ tag-start-line (line-number-at-pos tag-start-pos))
+ (and
+ ;; A "before" line which also starts an element begins with js, so
+ ;; indent it like js
+ (> current-line before-tag-line)
+ ;; Only indent the jsx lines like jsx
+ (>= current-line tag-start-line)))
+ (cond
+ ;; Analyze bounds if there are any
+ ((progn
+ (while (and (not tag-end-pos)
+ (setq last-pos (re-search-forward js2--jsx-end-tag-re
nil t)))
+ (while (forward-comment 1))
+ (when (looking-at js2--jsx-after-tag-re)
+ (setq tag-end-pos last-pos)))
+ tag-end-pos)
+ (setq tag-end-line (line-number-at-pos tag-end-pos)
+ after-tag-line (line-number-at-pos after-tag-line))
+ (or (and
+ ;; Ensure we're actually within the bounds of the jsx
+ (<= current-line tag-end-line)
+ ;; An "after" line which does not end an element begins with
+ ;; js, so indent it like js
+ (<= current-line after-tag-line))
+ (and
+ ;; Handle another case where there could be e.g. comments after
+ ;; the element
+ (> current-line tag-end-line)
+ (< current-line after-tag-line)
+ (setq type 'after))))
+ ;; They may not be any bounds (yet)
+ (t))
+ ;; Check if we're inside an embedded multi-line js expression
+ (cond
+ ((not type)
+ (goto-char current-pos)
+ (end-of-line)
+ (setq parens (nth 9 (syntax-ppss)))
+ (while (and parens (not type))
+ (setq paren (car parens))
+ (cond
+ ((and (>= paren tag-start-pos)
+ ;; Curly bracket indicates the start of an embedded
expression
+ (= (char-after paren) 123) ; {
+ ;; The first line of the expression is indented like sgml
+ (> current-line (line-number-at-pos paren))
+ ;; Check if within a closing curly bracket (if any)
+ ;; (exclusive, as the closing bracket is indented like sgml)
+ (cond
+ ((progn
+ (goto-char paren)
+ (ignore-errors (let (forward-sexp-function)
+ (forward-sexp))))
+ (< current-line (line-number-at-pos)))
+ (t)))
+ ;; Indicate this guy will be indented specially
+ (setq type 'expression))
+ (t (setq parens (cdr parens)))))
+ t)
+ (t))
+ (cond
+ (type)
+ ;; Indent the first jsx thing like js so we can indent future jsx
things
+ ;; like sgml relative to the first thing
+ ((= current-line tag-start-line) 'first)
+ ('nth))))))
+
+(defmacro js2--as-sgml (&rest body)
+ "Execute BODY as if in sgml-mode."
+ `(with-syntax-table sgml-mode-syntax-table
+ (let (forward-sexp-function
+ parse-sexp-lookup-properties)
+ ,@body)))
+
+(defun js2--expression-in-sgml-indent-line ()
+ "Indent the current line as JavaScript or SGML (whichever is farther)."
+ (let* (indent-col
+ (savep (point))
+ ;; Don't whine about errors/warnings when we're indenting.
+ ;; This has to be set before calling parse-partial-sexp below.
+ (inhibit-point-motion-hooks t)
+ (parse-status (save-excursion
+ (syntax-ppss (point-at-bol)))))
+ ;; Don't touch multiline strings.
+ (unless (nth 3 parse-status)
+ (setq indent-col (save-excursion
+ (back-to-indentation)
+ (if (>= (point) savep) (setq savep nil))
+ (js2--as-sgml (sgml-calculate-indent))))
+ (if (null indent-col)
+ 'noindent
+ ;; Use whichever indentation column is greater, such that the sgml
+ ;; column is effectively a minimum
+ (setq indent-col (max (js2-proper-indentation parse-status)
+ (+ indent-col js2-basic-offset)))
+ (if savep
+ (save-excursion (indent-line-to indent-col))
+ (indent-line-to indent-col))))))
+
+(defun js2-jsx-indent-line ()
+ "Indent the current line as JSX (with SGML offsets).
+i.e., customize JSX element indentation with `sgml-basic-offset'
+et al."
+ (interactive)
+ (let ((indentation-type (js2--jsx-indented-element-p)))
+ (cond
+ ((eq indentation-type 'expression)
+ (js2--expression-in-sgml-indent-line))
+ ((or (eq indentation-type 'first)
+ (eq indentation-type 'after))
+ ;; Don't treat this first thing as a continued expression (often a "<" or
+ ;; ">" causes this misinterpretation)
+ (cl-letf (((symbol-function #'js2-continued-expression-p) 'ignore))
+ (js2-indent-line)))
+ ((eq indentation-type 'nth)
+ (js2--as-sgml (sgml-indent-line)))
+ (t (js2-indent-line)))))
+
(provide 'js2-old-indent)
;;; js2-old-indent.el ends here
diff --git a/tests/indent.el b/tests/indent.el
index a0afeed..bacb614 100644
--- a/tests/indent.el
+++ b/tests/indent.el
@@ -31,14 +31,15 @@
(if keep-indent
s
(replace-regexp-in-string "^ *" "" s)))
- (js2-mode)
+ (js2-jsx-mode)
(indent-region (point-min) (point-max))
(should (string= s (buffer-substring-no-properties
(point-min) (point)))))))
(cl-defmacro js2-deftest-indent (name content &key bind keep-indent)
`(ert-deftest ,(intern (format "js2-%s" name)) ()
- (let ,(append '((js2-basic-offset 2)
+ (let ,(append '(indent-tabs-mode
+ (js2-basic-offset 2)
(js2-pretty-multiline-declarations t)
(inhibit-point-motion-hooks t))
bind)
@@ -169,3 +170,95 @@
| return 1;
|}"
:bind ((js2-indent-switch-body t)))
+
+(js2-deftest-indent jsx-one-line
+ "var foo = <div></div>;")
+
+(js2-deftest-indent jsx-children-parentheses
+ "return (
+ | <div>
+ | </div>
+ | <div>
+ | <div></div>
+ | <div>
+ | <div></div>
+ | </div>
+ | </div>
+ |);")
+
+(js2-deftest-indent jsx-children-unclosed
+ "return (
+ | <div>
+ | <div>")
+
+(js2-deftest-indent jsx-argument
+ "React.render(
+ | <div>
+ | <div></div>
+ | </div>,
+ | {
+ | a: 1
+ | },
+ | <div>
+ | <div></div>
+ | </div>
+ |);")
+
+(js2-deftest-indent jsx-leading-comment
+ "return (
+ | // Sneaky!
+ | <div></div>
+ |);")
+
+(js2-deftest-indent jsx-trailing-comment
+ "return (
+ | <div></div>
+ | // Sneaky!
+ |);")
+
+(js2-deftest-indent jsx-self-closing
+ ;; This ensures we know the bounds of a self-closing element
+ "React.render(
+ | <input
+ | />,
+ | {
+ | a: 1
+ | }
+ |);"
+ :bind ((sgml-attribute-offset 1))) ; Emacs 24.5 -> 25 compat
+
+(js2-deftest-indent jsx-embedded-js-content
+ "return (
+ | <div>
+ | {array.map(function () {
+ | return {
+ | a: 1
+ | };
+ | })}
+ | </div>
+ |);")
+
+(js2-deftest-indent jsx-embedded-js-unclosed
+ "return (
+ | <div>
+ | {array.map(function () {
+ | return {
+ | a: 1")
+
+(js2-deftest-indent jsx-embedded-js-attribute
+ "return (
+ | <div attribute={array.map(function () {
+ | return {
+ | a: 1
+ | };
+ |
+ | return {
+ | a: 1
+ | };
+ |
+ | return {
+ | a: 1
+ | };
+ | })}>
+ | </div>
+ |);")
- [elpa] master 2e7fdb3 012/110: Also highlight property lookups with the same face, (continued)
- [elpa] master 2e7fdb3 012/110: Also highlight property lookups with the same face, Dmitry Gutov, 2016/06/22
- [elpa] master b1234ca 013/110: Don't misindent generator methods, Dmitry Gutov, 2016/06/22
- [elpa] master d82a58d 019/110: Refactor: rename getter-setter-node to method-node, Dmitry Gutov, 2016/06/22
- [elpa] master 263852b 026/110: Add melpa badges, Dmitry Gutov, 2016/06/22
- [elpa] master 98433fb 036/110: `js2-print-json-path' added, Dmitry Gutov, 2016/06/22
- [elpa] master f9d28d3 052/110: Adjust js2-print-export-node, Dmitry Gutov, 2016/06/22
- [elpa] master d307285 039/110: Move method type handling to property METHOD_TYPE, Dmitry Gutov, 2016/06/22
- [elpa] master f652d17 044/110: Add setImmediate and clearImmediate to js2-node-externs, Dmitry Gutov, 2016/06/22
- [elpa] master e0972fb 048/110: s/will/would, Dmitry Gutov, 2016/06/22
- [elpa] master d201a30 027/110: Merge pull request #281 from robbyoconnor/patch-1, Dmitry Gutov, 2016/06/22
- [elpa] master 667d320 034/110: Merge pull request #282 from jacksonrayhamilton/js2-jsx-mode,
Dmitry Gutov <=
- [elpa] master 29f74b1 040/110: Allow 0o octals in strict mode, Dmitry Gutov, 2016/06/22
- [elpa] master 2a22635 035/110: Support .jsx file extension too in the example, Dmitry Gutov, 2016/06/22
- [elpa] master 4955584 037/110: Merge pull request #278 from redguardtoo/master, Dmitry Gutov, 2016/06/22
- [elpa] master 7c12f2c 031/110: Copy JSX indentation logic to js2-old-indent.el, Dmitry Gutov, 2016/06/22
- [elpa] master d0e4323 046/110: Add NEWS entry for async/await, Dmitry Gutov, 2016/06/22
- [elpa] master 0b1704b 041/110: Merge pull request #284 from XeCycle/0o-octals, Dmitry Gutov, 2016/06/22
- [elpa] master 7abe15e 043/110: Merge pull request #285 from dgreensp/fix-param-destruct, Dmitry Gutov, 2016/06/22
- [elpa] master 38aa66b 054/110: Do not print trailing semicolon for export function and class, Dmitry Gutov, 2016/06/22
- [elpa] master 4cd81c7 050/110: Exported decls print a little funny; update tests, Dmitry Gutov, 2016/06/22
- [elpa] master 00a455f 009/110: Merge pull request #266 from jacksonrayhamilton/no-getter-setter-duplicate, Dmitry Gutov, 2016/06/22