aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJackson Ray Hamilton2019-02-11 03:00:34 -0800
committerJackson Ray Hamilton2019-04-08 22:48:21 -0700
commitbe86ece42cbb6204480c794d018b02fbda74689b (patch)
treea4734ce3c80cfc605851c11c320f544135c4d968
parent27e9bce77db54464737aa5be1ce7142b55f25952 (diff)
downloademacs-be86ece42cbb6204480c794d018b02fbda74689b.tar.gz
emacs-be86ece42cbb6204480c794d018b02fbda74689b.zip
js-syntax-propertize: Disambiguate JS from JSX, fixing some indents
Fix some JSX indentation bugs: - Bug#24896 / https://github.com/mooz/js2-mode/issues/389 - Bug#30225 - https://github.com/mooz/js2-mode/issues/459 * lisp/progmodes/js.el (js--dotted-captured-name-re) (js--unary-keyword-re, js--unary-keyword-p) (js--disambiguate-beginning-of-jsx-tag) (js--disambiguate-end-of-jsx-tag) (js--disambiguate-js-from-jsx): New variables and functions. (js-syntax-propertize): Additionally clarify when syntax is JS so that ‘(with-syntax-table sgml-mode-syntax-table …)’ does not mistake some JS punctuation syntax for SGML parenthesis syntax, namely ‘<’ and ‘>’. * test/manual/indent/js-jsx-unclosed-2.js: Add additional test for unary operator parsing.
-rw-r--r--lisp/progmodes/js.el100
-rw-r--r--test/manual/indent/js-jsx-unclosed-2.js14
2 files changed, 113 insertions, 1 deletions
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
index 5b992535a8c..d0556f3538e 100644
--- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -82,6 +82,10 @@
82 (concat js--name-re "\\(?:\\." js--name-re "\\)*") 82 (concat js--name-re "\\(?:\\." js--name-re "\\)*")
83 "Regexp matching a dot-separated sequence of JavaScript names.") 83 "Regexp matching a dot-separated sequence of JavaScript names.")
84 84
85(defconst js--dotted-captured-name-re
86 (concat "\\(" js--name-re "\\)\\(?:\\." js--name-re "\\)*")
87 "Like `js--dotted-name-re', but capture the first name.")
88
85(defconst js--cpp-name-re js--name-re 89(defconst js--cpp-name-re js--name-re
86 "Regexp matching a C preprocessor name.") 90 "Regexp matching a C preprocessor name.")
87 91
@@ -1731,6 +1735,99 @@ This performs fontification according to `js--class-styles'."
1731 'syntax-table (string-to-syntax "\"/")) 1735 'syntax-table (string-to-syntax "\"/"))
1732 (goto-char end))))) 1736 (goto-char end)))))
1733 1737
1738(defconst js--unary-keyword-re
1739 (js--regexp-opt-symbol '("await" "delete" "typeof" "void" "yield"))
1740 "Regexp matching unary operator keywords.")
1741
1742(defun js--unary-keyword-p (string)
1743 "Check if STRING is a unary operator keyword in JavaScript."
1744 (string-match-p js--unary-keyword-re string))
1745
1746(defun js--disambiguate-beginning-of-jsx-tag ()
1747 "Parse enough to determine if a JSX tag starts here.
1748Disambiguate JSX from equality operators by testing for syntax
1749only valid as JSX."
1750 ;; “</…” - a JSXClosingElement.
1751 ;; “<>” - a JSXOpeningFragment.
1752 (if (memq (char-after) '(?\/ ?\>)) t
1753 (save-excursion
1754 (skip-chars-forward " \t\n")
1755 (and
1756 (looking-at js--dotted-captured-name-re)
1757 ;; Don’t match code like “if (i < await foo)”
1758 (not (js--unary-keyword-p (match-string 1)))
1759 (progn
1760 (goto-char (match-end 0))
1761 (skip-chars-forward " \t\n")
1762 (or
1763 ;; “>”, “/>” - tag enders.
1764 ;; “{” - a JSXExpressionContainer.
1765 (memq (char-after) '(?\> ?\/ ?\{))
1766 ;; Check if a JSXAttribute follows.
1767 (looking-at js--name-start-re)))))))
1768
1769(defun js--disambiguate-end-of-jsx-tag ()
1770 "Parse enough to determine if a JSX tag ends here.
1771Disambiguate JSX from equality operators by testing for syntax
1772only valid as JSX, or extremely unlikely except as JSX."
1773 (save-excursion
1774 (backward-char)
1775 ;; “…/>” - a self-closing JSXOpeningElement.
1776 ;; “</>” - a JSXClosingFragment.
1777 (if (= (char-before) ?/) t
1778 (let (last-tag-or-attr-name last-non-unary-p)
1779 (catch 'match
1780 (while t
1781 (skip-chars-backward " \t\n")
1782 ;; Check if the end of a JSXAttribute value or
1783 ;; JSXExpressionContainer almost certainly precedes.
1784 ;; The only valid JS this misses is
1785 ;; - {} > foo
1786 ;; - "bar" > foo
1787 ;; which is no great loss, IMHO…
1788 (if (memq (char-before) '(?\} ?\" ?\' ?\`)) (throw 'match t)
1789 (if (and last-tag-or-attr-name last-non-unary-p
1790 ;; “<”, “</” - tag starters.
1791 (memq (char-before) '(?\< ?\/)))
1792 ;; Leftmost name parsed was the name of a
1793 ;; JSXOpeningElement.
1794 (throw 'match t))
1795 ;; Technically the dotted name could span multiple
1796 ;; lines, but dear God WHY?! Also, match greedily to
1797 ;; ensure the entire name is valid.
1798 (if (looking-back js--dotted-captured-name-re (point-at-bol) t)
1799 (if (and (setq last-non-unary-p (not (js--unary-keyword-p (match-string 1))))
1800 last-tag-or-attr-name)
1801 ;; Valid (non-unary) name followed rightwards by
1802 ;; another name (any will do, including
1803 ;; keywords) is invalid JS, but valid JSX.
1804 (throw 'match t)
1805 ;; Remember match and skip backwards over it when
1806 ;; it is the first matched name or the N+1th
1807 ;; matched unary name (unary names on the left are
1808 ;; still ambiguously JS or JSX, so keep parsing to
1809 ;; disambiguate).
1810 (setq last-tag-or-attr-name (match-string 1))
1811 (goto-char (match-beginning 0)))
1812 ;; Nothing else to look for; give up parsing.
1813 (throw 'match nil)))))))))
1814
1815(defun js--disambiguate-js-from-jsx (start end)
1816 "Figure out which ‘<’ and ‘>’ chars (from START to END) aren’t JSX.
1817
1818Later, this info prevents ‘sgml-’ functions from treating some
1819‘<’ and ‘>’ chars as parts of tokens of SGML tags — a good thing,
1820since they are serving their usual function as some JS equality
1821operator or arrow function, instead."
1822 (goto-char start)
1823 (while (re-search-forward "[<>]" end t)
1824 (unless (if (eq (char-before) ?<) (js--disambiguate-beginning-of-jsx-tag)
1825 (js--disambiguate-end-of-jsx-tag))
1826 ;; Inform sgml- functions that this >, >=, >>>, <, <=, <<<, or
1827 ;; => token is punctuation (and not an open or close parenthesis
1828 ;; as per usual in sgml-mode).
1829 (put-text-property (1- (point)) (point) 'syntax-table '(1)))))
1830
1734(defun js-syntax-propertize (start end) 1831(defun js-syntax-propertize (start end)
1735 ;; JavaScript allows immediate regular expression objects, written /.../. 1832 ;; JavaScript allows immediate regular expression objects, written /.../.
1736 (goto-char start) 1833 (goto-char start)
@@ -1758,7 +1855,8 @@ This performs fontification according to `js--class-styles'."
1758 'syntax-table (string-to-syntax "\"/")) 1855 'syntax-table (string-to-syntax "\"/"))
1759 (js-syntax-propertize-regexp end))))) 1856 (js-syntax-propertize-regexp end)))))
1760 ("\\`\\(#\\)!" (1 "< b"))) 1857 ("\\`\\(#\\)!" (1 "< b")))
1761 (point) end)) 1858 (point) end)
1859 (if js-jsx-syntax (js--disambiguate-js-from-jsx start end)))
1762 1860
1763(defconst js--prettify-symbols-alist 1861(defconst js--prettify-symbols-alist
1764 '(("=>" . ?⇒) 1862 '(("=>" . ?⇒)
diff --git a/test/manual/indent/js-jsx-unclosed-2.js b/test/manual/indent/js-jsx-unclosed-2.js
index 2d42cf70f84..8b6f33325d7 100644
--- a/test/manual/indent/js-jsx-unclosed-2.js
+++ b/test/manual/indent/js-jsx-unclosed-2.js
@@ -15,3 +15,17 @@ if (foo > bar) void 0
15// Don’t even misinterpret unary operators as JSX. 15// Don’t even misinterpret unary operators as JSX.
16if (foo < await bar) void 0 16if (foo < await bar) void 0
17while (await foo > bar) void 0 17while (await foo > bar) void 0
18
19// Allow unary keyword names as null-valued JSX attributes.
20// (As if this will EVER happen…)
21<Foo yield>
22 <Bar void>
23 <Baz
24 zorp
25 typeof>
26 <Please do_n0t delete this_stupidTest >
27 How would we ever live without unary support
28 </Please>
29 </Baz>
30 </Bar>
31</Foo>