Improve JSX syntax propertization

* lisp/progmodes/js.el (js-jsx--attribute-name-re): New variable.
(js-jsx--syntax-propertize-tag): Allow “-” in JSXAttribute names.  Fix
“out of range” error when typing at the end of a buffer.  Fix/improve
future propertization of unfinished JSXBoundaryElements.

* test/manual/indent/js-jsx-unclosed-2.js: Add tests for allowed
characters in JSX.
This commit is contained in:
Jackson Ray Hamilton 2019-03-24 09:55:14 -07:00
parent bf37078df2
commit 8b92719b6b
No known key found for this signature in database
GPG key ID: B4771664B476B290
2 changed files with 51 additions and 31 deletions

View file

@ -2083,11 +2083,15 @@ been propertized."
(throw 'stop nil)))
(setq text-beg (point))))))
(defconst js-jsx--attribute-name-re (concat js--name-start-re
"\\(?:\\s_\\|\\sw\\|-\\)*")
"Like `js--name-re', but matches “-” as well.")
(defun js-jsx--syntax-propertize-tag (end)
"Determine if a JSXBoundaryElement is before END and propertize it.
Disambiguate JSX from inequality operators and arrow functions by
testing for syntax only valid as JSX."
(let ((tag-beg (1- (point))) (type 'open)
(let ((tag-beg (1- (point))) tag-end (type 'open)
name-beg name-match-data unambiguous
forward-sexp-function) ; Use Lisp version.
(catch 'stop
@ -2127,46 +2131,54 @@ testing for syntax only valid as JSX."
;; figure out what type it actually is.
(if (eq type 'open) (setq type (if name-beg 'self-closing 'close)))
(forward-char))
((looking-at js--dotted-name-re)
(if (not name-beg)
(progn
;; Dont match code like “if (i < await foo)”
(if (js--unary-keyword-p (match-string 0)) (throw 'stop nil))
;; Save boundaries for later fontification after
;; unambiguously determining the code is JSX.
(setq name-beg (match-beginning 0)
name-match-data (match-data))
(goto-char (match-end 0)))
(setq unambiguous t) ; Non-unary name followed by 2nd name ⇒ JSX
;; Save JSXAttributes names match data for font-locking later.
(put-text-property (match-beginning 0) (1+ (match-beginning 0))
'js-jsx-attribute-name (match-data))
(goto-char (match-end 0))
((and (not name-beg) (looking-at js--dotted-name-re))
;; Dont match code like “if (i < await foo)”
(if (js--unary-keyword-p (match-string 0)) (throw 'stop nil))
;; Save boundaries for later fontification after
;; unambiguously determining the code is JSX.
(setq name-beg (match-beginning 0)
name-match-data (match-data))
(goto-char (match-end 0)))
((and name-beg (looking-at js-jsx--attribute-name-re))
(setq unambiguous t) ; Non-unary name followed by 2nd name ⇒ JSX
;; Save JSXAttributes names match data for font-locking later.
(put-text-property (match-beginning 0) (1+ (match-beginning 0))
'js-jsx-attribute-name (match-data))
(goto-char (match-end 0))
(if (>= (point) end) (throw 'stop nil))
(skip-chars-forward " \t\n" end)
(if (>= (point) end) (throw 'stop nil))
;; “=” is optional for null-valued JSXAttributes.
(when (= (char-after) ?=)
(forward-char)
(if (>= (point) end) (throw 'stop nil))
(skip-chars-forward " \t\n" end)
(if (>= (point) end) (throw 'stop nil))
;; “=” is optional for null-valued JSXAttributes.
(when (= (char-after) ?=)
(forward-char)
(if (>= (point) end) (throw 'stop nil))
(skip-chars-forward " \t\n" end)
(if (>= (point) end) (throw 'stop nil))
;; Skip over strings (if possible). Any
;; JSXExpressionContainer here will be parsed in the
;; next iteration of the loop.
(when (memq (char-after) '(?\" ?\' ?\`))
(condition-case nil
(forward-sexp)
(scan-error (throw 'stop nil)))))))
;; Skip over strings (if possible). Any
;; JSXExpressionContainer here will be parsed in the
;; next iteration of the loop.
(when (memq (char-after) '(?\" ?\' ?\`))
(condition-case nil
(forward-sexp)
(scan-error (throw 'stop nil))))))
;; There is nothing more to check; this either isnt JSX, or
;; the tag is incomplete.
(t (throw 'stop nil)))))
(when unambiguous
;; Save JSXBoundaryElements names match data for font-locking.
(if name-beg (put-text-property name-beg (1+ name-beg) 'js-jsx-tag-name name-match-data))
;; Prevent “out of range” errors when typing at the end of a buffer.
(setq tag-end (if (eobp) (1- (point)) (point)))
;; Mark beginning and end of tag for font-locking.
(put-text-property tag-beg (1+ tag-beg) 'js-jsx-tag-beg (cons type (point)))
(put-text-property (point) (1+ (point)) 'js-jsx-tag-end tag-beg))
(put-text-property tag-beg (1+ tag-beg) 'js-jsx-tag-beg (cons type tag-end))
(put-text-property tag-end (1+ tag-end) 'js-jsx-tag-end tag-beg)
;; Use text properties to extend the syntax-propertize region
;; backward to the beginning of the JSXBoundaryElement in the
;; future. Typically the closing angle bracket could suggest
;; extending backward, but that would also involve more rigorous
;; parsing, and the closing angle bracket may not even exist yet
;; if the JSXBoundaryElement is still being typed.
(put-text-property tag-beg (1+ tag-end) 'syntax-multiline t))
(if (js-jsx--at-enclosing-tag-child-p) (js-jsx--syntax-propertize-tag-text end))))
(defconst js-jsx--text-properties

View file

@ -29,3 +29,11 @@ while (await foo > bar) void 0
</Baz>
</Bar>
</Foo>
// “-” is not allowed in a JSXBoundaryElements name.
<ABC />
<A-B-C /> // Weirdly-indented “continued expression.”
// “-” may be used in a JSXAttributes name.
<Foo a-b-c=""
x-y-z="" />