[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Emacs-diffs] master cf416d9 28/28: Explain reasonings for JSX syntax su
From: |
Jackson Ray Hamilton |
Subject: |
[Emacs-diffs] master cf416d9 28/28: Explain reasonings for JSX syntax support design decisions |
Date: |
Tue, 9 Apr 2019 02:00:17 -0400 (EDT) |
branch: master
commit cf416d96c2d5db2079ed37927f0926fe0386e68a
Author: Jackson Ray Hamilton <address@hidden>
Commit: Jackson Ray Hamilton <address@hidden>
Explain reasonings for JSX syntax support design decisions
* lisp/progmodes/js.el: Throughout the code, provide explanations for
why JSX support was implemented in the way that it was; in particular,
address the overlap between syntax-propertize-function, font-lock, and
indentation (as requested by Stefan).
---
lisp/progmodes/js.el | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 109 insertions(+)
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
index a1f5e69..535b703 100644
--- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -1536,6 +1536,25 @@ point of view of font-lock. It applies highlighting
directly with
;; Matcher always "fails"
nil)
+;; It wouldn’t be sufficient to font-lock JSX with mere regexps, since
+;; a JSXElement may be nested inside a JS expression within the
+;; boundaries of a parent JSXOpeningElement, and such a hierarchy
+;; ought to be fontified like JSX, JS, and JSX respectively:
+;;
+;; <div attr={void(<div></div>) && void(0)}></div>
+;;
+;; <div attr={ ← JSX
+;; void( ← JS
+;; <div></div> ← JSX
+;; ) && void(0) ← JS
+;; }></div> ← JSX
+;;
+;; `js-syntax-propertize' unambiguously identifies JSX syntax,
+;; including when it’s nested.
+;;
+;; Using a matcher function for each relevant part, retrieve match
+;; data recorded as syntax properties for fontification.
+
(defconst js-jsx--font-lock-keywords
`((js-jsx--match-tag-name 0 font-lock-function-name-face t)
(js-jsx--match-attribute-name 0 font-lock-variable-name-face t)
@@ -1861,6 +1880,27 @@ This performs fontification according to
`js--class-styles'."
"Check if STRING is a unary operator keyword in JavaScript."
(string-match-p js--unary-keyword-re string))
+;; Adding `syntax-multiline' text properties to JSX isn’t sufficient
+;; to identify multiline JSX when first typing it. For instance, if
+;; the user is typing a JSXOpeningElement for the first time…
+;;
+;; <div
+;; ^ (point)
+;;
+;; …and the user inserts a line break after the tag name (before the
+;; JSXOpeningElement starting on that line has been unambiguously
+;; identified as such), then the `syntax-propertize' region won’t be
+;; extended backwards to the start of the JSXOpeningElement:
+;;
+;; <div ← This line wasn’t JSX when last edited.
+;; attr=""> ← Despite completing the JSX, the next
+;; ^ `syntax-propertize' region wouldn’t magically
+;; extend back a few lines.
+;;
+;; Therefore, to try and recover from this scenario, parse backward
+;; from “>” to try and find the start of JSXBoundaryElements, and
+;; extend the `syntax-propertize' region there.
+
(defun js--syntax-propertize-extend-region (start end)
"Extend the START-END region for propertization, if necessary.
For use by `syntax-propertize-extend-region-functions'."
@@ -1903,6 +1943,23 @@ For use by `syntax-propertize-extend-region-functions'."
(throw 'stop nil)))))))
(if new-start (cons new-start end))))
+;; When applying syntax properties, since `js-syntax-propertize' uses
+;; `syntax-propertize-rules' to parse JSXBoundaryElements iteratively
+;; and statelessly, whenever we exit such an element, we need to
+;; determine the JSX depth. If >0, then we know we to apply syntax
+;; properties to JSXText up until the next JSXBoundaryElement occurs.
+;; But if the JSX depth is 0, then—importantly—we know to NOT parse
+;; the following code as JSXText, rather propertize it as regular JS
+;; as long as warranted.
+;;
+;; Also, when indenting code, we need to know if the code we’re trying
+;; to indent is on the 2nd or later line of multiline JSX, in which
+;; case the code is indented according to XML-like JSX conventions.
+;;
+;; For the aforementioned reasons, we find ourselves needing to
+;; determine whether point is enclosed in JSX or not; and, if so,
+;; where the JSX is. The following functions provide that knowledge.
+
(defconst js-jsx--tag-start-re
(concat "\\(" js--dotted-name-re "\\)\\(?:"
;; Whitespace is only necessary if an attribute implies JSX.
@@ -2004,6 +2061,24 @@ JSXElement or a JSXOpeningElement/JSXClosingElement
pair."
(let ((pos (save-excursion (js-jsx--enclosing-tag-pos))))
(and pos (>= (point) (nth 1 pos)))))
+;; We implement `syntax-propertize-function' logic fully parsing JSX
+;; in order to provide very accurate JSX indentation, even in the most
+;; complex cases (e.g. to indent JSX within a JS expression within a
+;; JSXAttribute…), as over the years users have requested this. Since
+;; we find so much information during this parse, we later use some of
+;; the useful bits for font-locking, too.
+;;
+;; Some extra effort is devoted to ensuring that no code which could
+;; possibly be valid JS is ever misinterpreted as partial JSX, since
+;; that would be regressive.
+;;
+;; We first parse trying to find the minimum number of components
+;; necessary to unambiguously identify a JSXBoundaryElement, even if
+;; it is a partial one. If a complete one is parsed, we move on to
+;; parse any JSXText. When that’s terminated, we unwind back to the
+;; `syntax-propertize-rules' loop so the next JSXBoundaryElement can
+;; be parsed, if any, be it an opening or closing one.
+
(defun js-jsx--text-range (beg end)
"Identify JSXText within a “>/{/}/<” pair."
(when (> (- end beg) 0)
@@ -2023,6 +2098,10 @@ JSXElement or a JSXOpeningElement/JSXClosingElement
pair."
;; JSXText determines JSXText context from earlier lines.
(put-text-property beg end 'syntax-multiline t)))
+;; In order to respect the end boundary `syntax-propertize-function'
+;; sets, care is taken in the following functions to abort parsing
+;; whenever that boundary is reached.
+
(defun js-jsx--syntax-propertize-tag-text (end)
"Determine if JSXText is before END and propertize it.
Text within an open/close tag pair may be JSXText. Temporarily
@@ -2562,6 +2641,21 @@ current line is the \"=>\" token (of an arrow function)."
(end-of-line)
(re-search-backward js--line-terminating-arrow-re from t)))
+;; When indenting, we want to know if the line is…
+;;
+;; - within a multiline JSXElement, or
+;; - within a string in a JSXBoundaryElement, or
+;; - within JSXText, or
+;; - within a JSXAttribute’s multiline JSXExpressionContainer.
+;;
+;; In these cases, special XML-like indentation rules for JSX apply.
+;; If JS is nested within JSX, then indentation calculations may be
+;; combined, such that JS indentation is “relative” to the JSX’s.
+;;
+;; Therefore, functions below provide such contextual information, and
+;; `js--proper-indentation' may call itself once recursively in order
+;; to finish calculating that “relative” JS+JSX indentation.
+
(defun js-jsx--context ()
"Determine JSX context and move to enclosing JSX."
(let ((pos (point))
@@ -4319,6 +4413,10 @@ their `mode-name' updates to show enabled syntax
extensions."
(interactive)
(setq-local js-jsx-syntax t))
+;; To make discovering and using syntax extensions features easier for
+;; users (who might not read the docs), try to safely and
+;; automatically enable syntax extensions based on heuristics.
+
(defvar js-jsx-regexps
(list "\\_<\\(?:var\\|let\\|const\\|import\\)\\_>.*?React")
"Regexps for detecting JSX in JavaScript buffers.
@@ -4444,6 +4542,17 @@ This function is intended for use in
`after-change-functions'."
;;(syntax-propertize (point-max))
)
+;; Since we made JSX support available and automatically-enabled in
+;; the base `js-mode' (for ease of use), now `js-jsx-mode' simply
+;; serves as one other interface to unconditionally enable JSX in
+;; buffers, mostly for backwards-compatibility.
+;;
+;; Since it is probably more common for packages to integrate with
+;; `js-mode' than with `js-jsx-mode', it is therefore probably
+;; slightly better for users to use one of the many other methods for
+;; enabling JSX syntax. But using `js-jsx-mode' can’t be that bad
+;; either, so we won’t bother users with an obsoletion warning.
+
;;;###autoload
(define-derived-mode js-jsx-mode js-mode "JavaScript"
"Major mode for editing JavaScript+JSX.
- [Emacs-diffs] master 3eadf1e 24/28: Identify JSX strings (for js2-mode), (continued)
- [Emacs-diffs] master 3eadf1e 24/28: Identify JSX strings (for js2-mode), Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 4b305bb 02/28: Refactor JSX indentation code to improve enclosing JSX discovery, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 58c77f1 01/28: Add failing tests for JSX indentation bugs, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 4d2b5bb 07/28: Font-lock JSX while editing it by extending regions, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 462baab 19/28: Add tests for miscellaneous JSX parsing feats, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master be86ece 04/28: js-syntax-propertize: Disambiguate JS from JSX, fixing some indents, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master d9d1bb2 14/28: Rename tests to use the “.jsx” file extension, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 84b1cfb 15/28: Indent broken arrow function bodies as an N+1th arg, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 6f53576 05/28: Use js-jsx- prefix for functions and variables, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master bf37078 12/28: Automatically detect JSX in JavaScript files, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master cf416d9 28/28: Explain reasonings for JSX syntax support design decisions,
Jackson Ray Hamilton <=
- [Emacs-diffs] master 9545519 26/28: Add open/close parenthesis syntax to “<” and “>” in JSX, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 7c3ffda 27/28: Move curly functions closer to where they’re used, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 1a1ef28 10/28: Indent JSX as parsed in a JS context, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 55c80d4 17/28: Indent expressions in JSXAttributes relative to the attribute’s name, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 18bbfc4 25/28: Permit non-ASCII identifiers in JS, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 98e36a3 21/28: Optimize js-jsx--enclosing-tag-pos, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 7a9dac5 22/28: Improve whitespace and unary keyword parsing, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master 8b92719 13/28: Improve JSX syntax propertization, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master afec451 18/28: Split JSX indentation calculation into several functions, Jackson Ray Hamilton, 2019/04/09
- [Emacs-diffs] master e48306f 23/28: Properly set a dynamic, syntactic mode name, Jackson Ray Hamilton, 2019/04/09