diff options
Diffstat (limited to 'configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645')
4 files changed, 1422 insertions, 0 deletions
diff --git a/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode-autoloads.el b/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode-autoloads.el new file mode 100644 index 000000000000..1323470ce1f5 --- /dev/null +++ b/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode-autoloads.el @@ -0,0 +1,43 @@ +;;; rjsx-mode-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil "rjsx-mode" "rjsx-mode.el" (23450 31811 168601 +;;;;;; 424000)) +;;; Generated autoloads from rjsx-mode.el + +(autoload 'rjsx-mode "rjsx-mode" "\ +Major mode for editing JSX files. + +\(fn)" t nil) + +(autoload 'rjsx-minor-mode "rjsx-mode" "\ +Minor mode for parsing JSX syntax into an AST. + +\(fn &optional ARG)" t nil) + +(add-to-list 'auto-mode-alist '("\\.jsx\\'" . rjsx-mode)) + +(autoload 'rjsx-comment-dwim "rjsx-mode" "\ +RJSX implementation of `comment-dwim'. If called on a region, +this function delegates to `comment-or-uncomment-region'. If the +point is not in a JSX context, it delegates to the +`comment-dwim', otherwise it will comment the JSX AST node at +point using the apppriate comment delimiters. + +For example: If point is on a JSX attribute or JSX expression, it +will comment the entire attribute using \"/* */\". , otherwise if +it's on a descendent JSX Element, it will use \"{/* */}\" +instead. + +\(fn ARG)" t nil) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; rjsx-mode-autoloads.el ends here diff --git a/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode-pkg.el b/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode-pkg.el new file mode 100644 index 000000000000..237f0b804083 --- /dev/null +++ b/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "rjsx-mode" "20180823.1645" "Real support for JSX" '((emacs "24.4") (js2-mode "20170504")) :commit "c463a162a2557668f624c32dd5c3b58512e80b1a" :keywords '("languages") :authors '(("Felipe Ochoa" . "felipe@fov.space")) :maintainer '("Felipe Ochoa" . "felipe@fov.space") :url "https://github.com/felipeochoa/rjsx-mode/") diff --git a/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode.el b/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode.el new file mode 100644 index 000000000000..9f43b1699586 --- /dev/null +++ b/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode.el @@ -0,0 +1,1377 @@ +;;; rjsx-mode.el --- Real support for JSX -*- lexical-binding: t -*- + +;; Copyright (C) 2016 Felipe Ochoa + +;; Author: Felipe Ochoa <felipe@fov.space> +;; URL: https://github.com/felipeochoa/rjsx-mode/ +;; Package-Version: 20180823.1645 +;; Package-Requires: ((emacs "24.4") (js2-mode "20170504")) +;; Version: 1.1 +;; Keywords: languages + +;;; Commentary: +;; Defines a major mode `rjsx-mode' based on `js2-mode' for editing +;; JSX files. `rjsx-mode' extends the parser in `js2-mode' to support +;; the full JSX syntax. This means you get all of the `js2' features +;; plus proper syntax checking and highlighting of JSX code blocks. +;; +;; Some features that this mode adds to js2: +;; +;; - Highlighting JSX tag names and attributes (using the rjsx-tag and +;; rjsx-attr faces) +;; - Highlight undeclared JSX components +;; - Parsing the spread operator {...otherProps} +;; - Parsing && and || in child expressions {cond && <BigComponent/>} +;; - Parsing ternary expressions {toggle ? <ToggleOn /> : <ToggleOff />} +;; +;; Additionally, since rjsx-mode extends the js2 AST, utilities using +;; the parse tree gain access to the JSX structure. + +;;; Code: + +;;;; Basic mode definitions + +(require 'cl-lib) +(require 'js2-mode) +(eval-when-compile (require 'subr-x)) +(require 'newcomment) + +(defgroup rjsx-mode nil + "Support for JSX." + :group 'js2-mode) + +(defcustom rjsx-max-size-for-frequent-reparse 100000 + "Buffers with fewer than this many characters will be parsed more frequently. +Set this to 0 to disable the reparsing altogether. The frequent +parsing supports the magic `rjsx-electric-lt' and +`rjsx-delete-creates-full-tag' behaviors." + :group 'rjsx-mode + :type 'integer) + +;;;###autoload +(define-derived-mode rjsx-mode js2-jsx-mode "RJSX" + "Major mode for editing JSX files." + :lighter ":RJSX" + :group 'rjsx-mode + (setq-local comment-use-syntax nil) + (setq-local comment-start-skip "[[:space:]]*\\(//+\\|{?/\\*+\\)") + ;; \n is included to get arround `comment-normalize-vars' and `comment-only-p' + (setq-local comment-end-skip "\\(\\*+/}?[[:space:]]*\\)\n?\\|\n") + (setq-local comment-region-function 'rjsx-comment-region-function) + (setq-local uncomment-region-function 'rjsx-uncomment-region-function) + (setq-local comment-quote-nested-function 'rjsx-comment-quote-nested-function) + (setq-local indent-line-function 'rjsx-indent-line) + (setq-local indent-region-function 'rjsx-indent-region)) + +;;;###autoload +(define-minor-mode rjsx-minor-mode + "Minor mode for parsing JSX syntax into an AST." + :lighter " rjsx" + (if rjsx-minor-mode + (js2-minor-mode 1) + (js2-minor-mode 0))) + +;;;###autoload +(add-to-list 'auto-mode-alist '("\\.jsx\\'" . rjsx-mode)) + +(defun rjsx-parse-xml-initializer (orig-fun) + "Dispatch the xml parser based on variable `rjsx-mode' being active or not. +This function is used to advise `js2-parse-xml-initializer' (ORIG-FUN) using +the `:around' combinator. JS2-PARSER is the original XML parser." + (if (or (eq major-mode 'rjsx-mode) rjsx-minor-mode) + (rjsx-parse-top-xml) + (apply orig-fun nil))) + +(advice-add 'js2-parse-xml-initializer :around #'rjsx-parse-xml-initializer) + +(defun rjsx-unadvice-js2 () + "Remove the rjsx advice on the js2 parser. This will cause rjsx to stop working globally." + (advice-remove 'js2-parse-xml-initializer #'rjsx-parse-xml-initializer) + (advice-remove 'js--looking-at-operator-p #'rjsx--js--looking-at-operator-p-advice)) + + +(defface rjsx-tag + '((t . (:inherit font-lock-function-name-face))) + "`rjsx-mode' face used to highlight JSX tag names." + :group 'rjsx-mode) + +(defface rjsx-attr + '((t . (:inherit font-lock-variable-name-face))) + "`rjsx-mode' face used to highlight JSX attribute names." + :group 'rjsx-mode) + +(defface rjsx-text + '((t . (:inherit font-lock-string-face))) + "`rjsx-mode' face used to highlight JSX text." + :group 'rjsx-mode) + +(defface rjsx-tag-bracket-face + '((t . (:inherit default))) + "`rjsx-mode' face used to highlight `<', `/', and `>'." + :group 'rjsx-mode) + + +;;;; Parser constants struct definitions + +;; Token types for XML nodes. We need to re-use some unused values to +;; not mess up the vectors that js2 has set up +(defvar rjsx-JSX js2-ENUM_INIT_KEYS) +(defvar rjsx-JSX-CLOSE js2-ENUM_INIT_VALUES) +(defvar rjsx-JSX-IDENT js2-ENUM_INIT_ARRAY) +(defvar rjsx-JSX-MEMBER js2-ENUM_NEXT) +(defvar rjsx-JSX-ATTR js2-ENUM_ID) +(defvar rjsx-JSX-SPREAD js2-REF_NS_MEMBER) +(defvar rjsx-JSX-TEXT js2-ESCXMLTEXT) +(defvar rjsx-JSX-EXPRESSION js2-ESCXMLATTR) + +(dolist (sym '(rjsx-JSX rjsx-JSX-CLOSE rjsx-JSX-IDENT rjsx-JSX-MEMBER rjsx-JSX-ATTR + rjsx-JSX-SPREAD rjsx-JSX-TEXT rjsx-JSX-EXPRESSION)) + (aset js2-token-names (symbol-value sym) (downcase (substring (symbol-name sym) 5))) + (puthash sym (symbol-value sym) js2-token-codes)) + +(js2-msg "msg.bad.jsx.ident" "invalid JSX identifier") +(js2-msg "msg.invalid.jsx.string" "invalid JSX string (cannot contain delimiter in string body)") +(js2-msg "msg.mismatched.close.tag" "mismatched closing JSX tag; expected `%s'") +(js2-msg "msg.no.gt.in.opener" "missing `>' in opening tag") +(js2-msg "msg.no.gt.in.closer" "missing `>' in closing tag") +(js2-msg "msg.no.gt.after.slash" "missing `>' after `/' in self-closing tag") +(js2-msg "msg.no.rc.after.spread" "missing `}' after spread-prop") +(js2-msg "msg.no.value.after.jsx.prop" "missing value after prop `%s'") +(js2-msg "msg.no.dots.in.prop.spread" "missing `...' in spread prop") +(js2-msg "msg.no.rc.after.expr" "missing `}' after expression") +(js2-msg "msg.empty.expr" "empty `{}' expression") + + +(cl-defstruct (rjsx-node + (:include js2-node (type rjsx-JSX)) + (:constructor nil) + (:constructor make-rjsx-node + (&key (pos (js2-current-token-beg)) + len + name + rjsx-props + kids))) + name ; AST node containing the parsed xml name or nil for fragments + rjsx-props ; linked list of AST nodes (both attributes and spreads) + kids ; linked list of child xml nodes + closing-tag) ; AST node with the tag closer + + +(js2--struct-put 'rjsx-node 'js2-visitor 'rjsx-node-visit) +(js2--struct-put 'rjsx-node 'js2-printer 'rjsx-node-print) +(defun rjsx-node-visit (ast callback) + "Visit the `rjsx-node' children of AST, invoking CALLBACK on them." + (let ((name (rjsx-node-name ast))) + (when name (js2-visit-ast name callback))) + (dolist (prop (rjsx-node-rjsx-props ast)) + (js2-visit-ast prop callback)) + (dolist (prop (rjsx-node-kids ast)) + (js2-visit-ast prop callback)) + (when (rjsx-node-closing-tag ast) + (js2-visit-ast (rjsx-node-closing-tag ast) callback))) + +(defun rjsx-node-print (node indent-level) + "Print the `rjsx-node' NODE at indent level INDENT-LEVEL." + (insert (js2-make-pad indent-level) "<") + (let ((name-n (rjsx-node-name node))) + (when name-n (js2-print-ast name-n 0))) + (dolist (attr (rjsx-node-rjsx-props node)) + (insert " ") + (js2-print-ast attr 0)) + (let ((closer (rjsx-node-closing-tag node))) + (if (null closer) + (insert "/>") + (insert ">") + (dolist (child (rjsx-node-kids node)) + (js2-print-ast child 0)) + (js2-print-ast closer indent-level)))) + +(defun rjsx-node-opening-tag-name (node) + "Return a string with NODE's opening tag including any namespace and member operations." + (let ((name-n (rjsx-node-name node))) + (cond + ((null name-n) "") ; fragment + ((rjsx-member-p name-n) (rjsx-member-full-name name-n)) + ((rjsx-identifier-p name-n) (rjsx-identifier-full-name name-n)) + (t "")))) ; js2-error-node + +(defun rjsx-node-push-prop (n rjsx-prop) + "Extend rjsx-node N's rjsx-props with js2-node RJSX-PROP. +Sets JSX-PROPS's parent to N." + (let ((rjsx-props (rjsx-node-rjsx-props n))) + (if rjsx-props + (setcdr rjsx-props (nconc (cdr rjsx-props) (list rjsx-prop))) + (setf (rjsx-node-rjsx-props n) (list rjsx-prop)))) + (js2-node-add-children n rjsx-prop)) + +(defun rjsx-node-push-child (n kid) + "Extend rjsx-node N's children with js2-node KID. +Sets KID's parent to N." + (let ((kids (rjsx-node-kids n))) + (if kids + (setcdr kids (nconc (cdr kids) (list kid))) + (setf (rjsx-node-kids n) (list kid)))) + (js2-node-add-children n kid)) + + +(cl-defstruct (rjsx-closing-tag + (:include js2-node (type rjsx-JSX-CLOSE)) + (:constructor nil) + (:constructor make-rjsx-closing-tag (&key pos len name))) + name) ; An rjsx-identifier or rjsx-member node or nil for fragments + +(js2--struct-put 'rjsx-closing-tag 'js2-visitor 'rjsx-closing-tag-visit) +(js2--struct-put 'rjsx-closing-tag 'js2-printer 'rjsx-closing-tag-print) + +(defun rjsx-closing-tag-visit (ast callback) + "Visit the `rjsx-closing-tag' children of AST, invoking CALLBACK on them." + (js2-visit-ast (rjsx-closing-tag-name ast) callback)) + +(defun rjsx-closing-tag-print (node indent-level) + "Print the `rjsx-closing-tag' NODE at INDENT-LEVEL." + (insert (js2-make-pad indent-level) "</" (rjsx-closing-tag-full-name node) ">")) + +(defun rjsx-closing-tag-full-name (n) + "Return the string with N's fully-namespaced name, or just name if it's not namespaced." + (let ((child (rjsx-closing-tag-name n))) + (cond + ((null child) "") ; fragment + ((rjsx-member-p child) (rjsx-member-full-name child)) + ((rjsx-identifier-p child) (rjsx-identifier-full-name child)) + (t "")))) + +(cl-defstruct (rjsx-identifier + (:include js2-node (type rjsx-JSX-IDENT)) + (:constructor nil) + (:constructor make-rjsx-identifier (&key (pos (js2-current-token-beg)) + len namespace name))) + (namespace nil) + name) ; js2-name-node + +(js2--struct-put 'rjsx-identifier 'js2-visitor 'rjsx-identifier-visit) +(js2--struct-put 'rjsx-identifier 'js2-printer 'rjsx-identifier-print) + +(defun rjsx-identifier-visit (n callback) + "Visit N's children can call CALLBACK on them." + (js2-visit-ast (rjsx-identifier-name n) callback)) + +(defun rjsx-identifier-print (node indent-level) + "Print the `rjsx-identifier' NODE at INDENT-LEVEL." + (insert (js2-make-pad indent-level) (rjsx-identifier-full-name node))) + +(defun rjsx-identifier-full-name (n) + "Return the string with N's fully-namespaced name, or just name if it's not namespaced." + (if (rjsx-identifier-namespace n) + (format "%s:%s" (rjsx-identifier-namespace n) (js2-name-node-name (rjsx-identifier-name n))) + (js2-name-node-name (rjsx-identifier-name n)))) + +(cl-defstruct (rjsx-member + (:include js2-node (type rjsx-JSX-MEMBER)) + (:constructor nil) + (:constructor make-rjsx-member (&key pos len dots-pos idents))) + dots-pos ; List of positions of each dot + idents) ; List of rjsx-identifier nodes + +(js2--struct-put 'rjsx-member 'js2-visitor 'rjsx-member-visit) +(js2--struct-put 'rjsx-member 'js2-printer 'rjsx-member-print) + +(defun rjsx-member-visit (n callback) + "Visit N's children and call CALLBACK on them." + (dolist (ident (rjsx-member-idents n)) + (js2-visit-ast ident callback))) + +(defun rjsx-member-print (node indent-level) + "Print the `rjsx-member' NODE at INDENT-LEVEL." + (insert (js2-make-pad indent-level) (rjsx-member-full-name node))) + +(defun rjsx-member-full-name (n) + "Return the string with N's combined names together." + (mapconcat 'rjsx-identifier-full-name (rjsx-member-idents n) ".")) + +(cl-defstruct (rjsx-attr + (:include js2-node (type rjsx-JSX-ATTR)) + (:constructor nil) + (:constructor make-rjsx-attr (&key (pos (js2-current-token-beg)) + len name value))) + name ; a rjsx-identifier + value) ; a js2-expression + +(js2--struct-put 'rjsx-attr 'js2-visitor 'rjsx-attr-visit) +(js2--struct-put 'rjsx-attr 'js2-printer 'rjsx-attr-print) + +(defun rjsx-attr-visit (ast callback) + "Visit the `rjsx-attr' children of AST, invoking CALLBACK on them." + (js2-visit-ast (rjsx-attr-name ast) callback) + (js2-visit-ast (rjsx-attr-value ast) callback)) + +(defun rjsx-attr-print (node indent-level) + "Print the `rjsx-attr' NODE at INDENT-LEVEL." + (js2-print-ast (rjsx-attr-name node) indent-level) + (unless (js2-empty-expr-node-p (rjsx-attr-value node)) + (insert "=") + (js2-print-ast (rjsx-attr-value node) 0))) + +(cl-defstruct (rjsx-spread + (:include js2-node (type rjsx-JSX-SPREAD)) + (:constructor nil) + (:constructor make-rjsx-spread (&key pos len expr))) + expr) ; a js2-expression + +(js2--struct-put 'rjsx-spread 'js2-visitor 'rjsx-spread-visit) +(js2--struct-put 'rjsx-spread 'js2-printer 'rjsx-spread-print) + +(defun rjsx-spread-visit (ast callback) + "Visit the `rjsx-spread' children of AST, invoking CALLBACK on them." + (js2-visit-ast (rjsx-spread-expr ast) callback)) + +(defun rjsx-spread-print (node indent-level) + "Print the `rjsx-spread' NODE at INDENT-LEVEL." + (insert (js2-make-pad indent-level) "{...") + (js2-print-ast (rjsx-spread-expr node) 0) + (insert "}")) + +(cl-defstruct (rjsx-wrapped-expr + (:include js2-node (type rjsx-JSX-TEXT)) + (:constructor nil) + (:constructor make-rjsx-wrapped-expr (&key pos len child))) + child) + +(js2--struct-put 'rjsx-wrapped-expr 'js2-visitor 'rjsx-wrapped-expr-visit) +(js2--struct-put 'rjsx-wrapped-expr 'js2-printer 'rjsx-wrapped-expr-print) + +(defun rjsx-wrapped-expr-visit (ast callback) + "Visit the `rjsx-wrapped-expr' child of AST, invoking CALLBACK on them." + (js2-visit-ast (rjsx-wrapped-expr-child ast) callback)) + +(defun rjsx-wrapped-expr-print (node indent-level) + "Print the `rjsx-wrapped-expr' NODE at INDENT-LEVEL." + (insert (js2-make-pad indent-level) "{") + (js2-print-ast (rjsx-wrapped-expr-child node) indent-level) + (insert "}")) + +(cl-defstruct (rjsx-text + (:include js2-node (type rjsx-JSX-TEXT)) + (:constructor nil) + (:constructor make-rjsx-text (&key (pos (js2-current-token-beg)) + (len (js2-current-token-len)) + value))) + value) ; a string + +(js2--struct-put 'rjsx-text 'js2-visitor 'js2-visit-none) +(js2--struct-put 'rjsx-text 'js2-printer 'rjsx-text-print) + +(defun rjsx-text-print (node _indent-level) + "Print the `rjsx-text' NODE at INDENT-LEVEL." + ;; Text nodes include whitespace + (insert (rjsx-text-value node))) + + +;;;; Recursive descent parsing +(defvar rjsx-print-debug-message nil "If t will print out debug messages.") +;(setq rjsx-print-debug-message t) +(defmacro rjsx-maybe-message (&rest args) + "If debug is enabled, call `message' with ARGS." + `(when rjsx-print-debug-message + (message ,@args))) + + +(defvar rjsx-text-syntax-table + (let ((table (make-syntax-table (standard-syntax-table)))) + ;; `js-indent-line' assumes that \n is not whitespace + ;; Since it's not a comment delimiter in JSX text, punctuation + ;; is the only other (semi) logical choice + (modify-syntax-entry ?\n "." table) + table)) + +(js2-deflocal rjsx-in-xml nil "Variable used to track which xml parsing function is the outermost one.") + +(define-error 'rjsx-eof-while-parsing "RJSX: EOF while parsing") + +(defmacro rjsx-handling-eof (&rest body) + "Execute BODY and return the result of the last form. +If BODY signals `rjsx-eof-while-parsing', instead report a syntax +error and return a `js2-error-node'." + `(let ((beg (js2-current-token-beg))) + (condition-case nil + (progn ,@body) + (rjsx-eof-while-parsing + (let ((len (- js2-ts-cursor beg))) + (rjsx-maybe-message (format "Handling eof from %d" beg)) + (js2-report-error "msg.syntax" nil beg len) + (make-js2-error-node :pos beg :len len)))))) + +(defsubst rjsx-record-tag-bracket-face () + "Fontify the current token with `rjsx-tag-bracket-face'." + (js2-set-face (js2-current-token-beg) (js2-current-token-end) 'rjsx-tag-bracket-face 'record)) + +(defsubst rjsx-record-token-class (cls) + "Record an 'rjsx-class text property with value CLS for the current token." + (js2-record-text-property (js2-current-token-beg) (js2-current-token-end) + 'rjsx-class cls)) + +(defun rjsx-parse-top-xml () + "Parse a top level XML fragment. +This is the entry point when ‘js2-parse-unary-expr’ finds a '<' character" + (if rjsx-in-xml + (rjsx-parse-xml) + (let ((rjsx-in-xml t)) + (rjsx-handling-eof (rjsx-parse-xml))))) + +(defun rjsx-parse-xml () + "Parse a complete xml node from start to end tag." + (rjsx-record-token-class '<) + (rjsx-record-tag-bracket-face) + (let ((pn (make-rjsx-node)) self-closing name-n name-str child child-name-str is-fragment) + (rjsx-maybe-message "Starting rjsx-parse-xml after <") + (if (setq child (rjsx-parse-empty-tag)) + child + (if (eq (js2-peek-token) js2-GT) + (setq is-fragment t) + (setf (rjsx-node-name pn) (setq name-n (rjsx-parse-member-or-ns 'rjsx-tag))) + (if (js2-error-node-p name-n) + (progn (rjsx-maybe-message "could not parse tag name") + (make-js2-error-node :pos (js2-node-pos pn) :len (1+ (js2-node-len name-n)))) + (js2-node-add-children pn name-n) + (if js2-highlight-external-variables + (let ((name-node (rjsx-identifier-name + (if (rjsx-member-p name-n) + (car (rjsx-member-idents name-n)) + name-n))) + (case-fold-search nil)) + (when (string-match-p "^[[:upper:]]" (js2-name-node-name name-node)) + (js2-record-name-node name-node))))) + (rjsx-maybe-message "cleared tag name: '%s'" name-str) + ;; Now parse the attributes + (rjsx-parse-attributes pn) + (rjsx-maybe-message "cleared attributes")) + (setq name-str (rjsx-node-opening-tag-name pn)) + (progn + ;; Now parse either a self closing tag or the end of the opening tag + (rjsx-maybe-message "next type: `%s'" (js2-peek-token)) + (if (setq self-closing (js2-match-token js2-DIV)) + (progn + (rjsx-record-token-class 'self-closing-slash) + (rjsx-record-tag-bracket-face) + ;; TODO: How do we un-mark old slashes? + (when (js2-must-match js2-GT "msg.no.gt.after.slash" + (js2-node-pos pn) (- (js2-current-token-end) (js2-node-pos pn))) + (rjsx-record-token-class '>) + (rjsx-record-tag-bracket-face))) + (when (js2-must-match js2-GT "msg.no.gt.in.opener" (js2-node-pos pn) (js2-node-len pn)) + (rjsx-record-token-class '>) + (rjsx-record-tag-bracket-face))) + (rjsx-maybe-message "cleared opener closer, self-closing: %s" self-closing) + (if self-closing + (setf (js2-node-len pn) (- (js2-current-token-end) (js2-node-pos pn))) + (while (not (rjsx-closing-tag-p (setq child (rjsx-parse-child is-fragment)))) + ;; rjsx-parse-child calls our scanner, which always moves + ;; forward at least one character. If it hits EOF, it + ;; signals to our caller, so we don't have to worry about infinite loops here + (rjsx-maybe-message "parsed child") + (rjsx-node-push-child pn child) + (if (= 0 (js2-node-len child)) ; TODO: Does this ever happen? + (js2-get-token))) + (setq child-name-str (rjsx-closing-tag-full-name child)) + (unless (string= name-str child-name-str) + (js2-report-error "msg.mismatched.close.tag" name-str (js2-node-pos child) (js2-node-len child))) + (rjsx-maybe-message "cleared children for `%s'" name-str) + (js2-node-add-children pn child) + (setf (rjsx-node-closing-tag pn) child)) + (rjsx-maybe-message "Returning completed XML node") + (setf (js2-node-len pn) (- (js2-current-token-end) (js2-node-pos pn))) + pn)))) + +(defun rjsx-parse-empty-tag () + "Check if we are in an empty tag of the form `</>' and consume it if so. +Returns a `js2-error-node' if we are in one or nil if not." + (let ((beg (js2-current-token-beg))) + (when (js2-match-token js2-DIV) + (if (js2-match-token js2-GT) + (progn ; We're in a </> block, likely created by us in `rjsx-electric-lt' + ;; We only highlight the < to reduce the visual impact + (js2-report-error "msg.syntax" nil beg 1) + (make-js2-error-node :pos beg :len (- (js2-current-token-end) beg))) + ;; TODO: This is probably an unmatched closing tag. We should + ;; consume it, mark it an error, and move on + (js2-unget-token) + nil)))) + +(defun rjsx-parse-attributes (parent) + "Parse all attributes, including key=value and {...spread}, and add them to PARENT." + ;; Getting this function to not hang in the loop proved tricky. The + ;; key is that `rjsx-parse-spread' and `rjsx-parse-single-attr' both + ;; return `js2-error-node's if they fail to consume any tokens, + ;; which signals to us that we just need to discard one token and + ;; keep going. + (let (attr + (loop-terminators (list js2-DIV js2-GT js2-EOF js2-ERROR))) + (while (not (memql (js2-peek-token) loop-terminators)) + (rjsx-maybe-message "Starting loop. Next token type: %s\nToken pos: %s" (js2-peek-token) (js2-current-token-beg)) + (setq attr + (if (js2-match-token js2-LC) + (or (rjsx-check-for-empty-curlies t) + (prog1 (rjsx-parse-spread) + (rjsx-maybe-message "Parsed spread"))) + (rjsx-maybe-message "Parsing single attr") + (rjsx-parse-single-attr))) + (when (js2-error-node-p attr) (js2-get-token)) + ; TODO: We should make this conditional on + ; `js2-recover-from-parse-errors' + (rjsx-node-push-prop parent attr)))) + + +(cl-defun rjsx-check-for-empty-curlies (&optional dont-consume-rc &key check-for-comments warning) + "If the following token is '}' set empty curly errors. +If DONT-CONSUME-RC is non-nil, the matched right curly token +won't be consumed. Returns a `js2-error-node' if the curlies are +empty or nil otherwise. If CHECK-FOR-COMMENTS (a &KEY argument) +is non-nil, this will check for comments inside the curlies and +returns a `js2-empty-expr-node' if any are found. If WARNING (a +&key argument) is non-nil, reports the empty curlies as a warning +and not an error and also returns a `js2-empty-expr-node'. +Assumes the current token is a '{'." + (let ((beg (js2-current-token-beg)) end len) + (when (js2-match-token js2-RC) + (setq end (js2-current-token-end)) + (setq len (- end beg)) + (when dont-consume-rc + (js2-unget-token)) + (if check-for-comments (rjsx-maybe-message "Checking for comments between %d and %d" beg end)) + (unless (and check-for-comments + (dolist (comment js2-scanned-comments) + (rjsx-maybe-message "Comment at %d, length=%d" + (js2-node-pos comment) + (js2-node-len comment)) + ;; TODO: IF comments are in reverse document order, we should be able to + ;; bail out early and know we didn't find one + (when (and (>= (js2-node-pos comment) beg) + (<= (+ (js2-node-pos comment) (js2-node-len comment)) end)) + (cl-return-from rjsx-check-for-empty-curlies + (make-js2-empty-expr-node :pos beg :len (- end beg)))))) + (if warning + (progn (js2-report-warning "msg.empty.expr" nil beg len) + (make-js2-empty-expr-node :pos beg :len (- end beg))) + (js2-report-error "msg.empty.expr" nil beg len) + (make-js2-error-node :pos beg :len len)))))) + + +(defun rjsx-parse-spread () + "Parse an {...props} attribute." + (let ((pn (make-rjsx-spread :pos (js2-current-token-beg))) + (beg (js2-current-token-beg)) + missing-dots expr) + (setq missing-dots (not (js2-match-token js2-TRIPLEDOT))) + ;; parse-assign-expr will go crazy if we're looking at `} /', so we + ;; check for an empty spread first + (if (js2-match-token js2-RC) + (setq expr (make-js2-error-node :len 1)) + (setq expr (js2-parse-assign-expr)) + (when (js2-error-node-p expr) + (pop js2-parsed-errors))) ; We'll add our own error + (unless (or (js2-match-token js2-RC) (js2-error-node-p expr)) + (js2-report-error "msg.no.rc.after.spread" nil + beg (- (js2-current-token-end) beg))) + (setf (rjsx-spread-expr pn) expr) + (setf (js2-node-len pn) (- (js2-current-token-end) (js2-node-pos pn))) + (js2-node-add-children pn expr) + (if (js2-error-node-p expr) + (js2-report-error "msg.syntax" nil beg (- (js2-current-token-end) beg)) + (when missing-dots + (js2-report-error "msg.no.dots.in.prop.spread" nil beg (js2-node-len pn)))) + (if (= 0 (js2-node-len pn)) ; TODO: Is this ever possible? + (make-js2-error-node :pos beg :len 0) + pn))) + +(defun rjsx-parse-single-attr () + "Parse an 'a=b' JSX attribute and return the corresponding XML node." + (let ((pn (make-rjsx-attr)) name value beg) + (setq name (rjsx-parse-identifier 'rjsx-attr)) ; Won't consume token on error + (if (js2-error-node-p name) + name + (setf (rjsx-attr-name pn) name) + (setq beg (js2-node-pos name)) + (setf (js2-node-pos pn) beg) + (js2-node-add-children pn name) + (rjsx-maybe-message "Got the name for the attr: `%s'" (rjsx-identifier-full-name name)) + (if (js2-match-token js2-ASSIGN) ; Won't consume on error + (progn + (rjsx-maybe-message "Matched the equals sign") + (if (js2-match-token js2-LC) + (setq value (rjsx-parse-wrapped-expr nil t)) + (if (js2-match-token js2-STRING) + (setq value (rjsx-parse-string)) + (js2-report-error "msg.no.value.after.jsx.prop" (rjsx-identifier-full-name name) + beg (- (js2-current-token-end) beg)) + (setq value (make-js2-error-node :pos beg :len (js2-current-token-len)))))) + (setq value (make-js2-empty-expr-node :pos (js2-current-token-end) :len 0))) + (rjsx-maybe-message "value type: `%s'" (js2-node-type value)) + (setf (rjsx-attr-value pn) value) + (setf (js2-node-len pn) (- (js2-node-end value) (js2-node-pos pn))) + (js2-node-add-children pn value) + (rjsx-maybe-message "Finished single attribute.") + pn))) + +(defun rjsx-parse-wrapped-expr (allow-empty skip-to-rc) + "Parse a curly-brace-wrapped JS expression. +If ALLOW-EMPTY is non-nil, will warn for empty braces, otherwise +will signal a syntax error. If it does not find a right curly +and SKIP-TO-RC is non-nil, after the expression, consumes tokens +until the end of the JSX node" + (rjsx-maybe-message "parsing wrapped expression") + (let (pn + (beg (js2-current-token-beg)) + (child (rjsx-check-for-empty-curlies nil + :check-for-comments allow-empty + :warning allow-empty))) + (if child + (if allow-empty + (make-rjsx-wrapped-expr :pos beg :len (js2-node-len child) :child child) + child) ;; Will be an error node in this case + (setq child (js2-parse-assign-expr)) + (rjsx-maybe-message "parsed expression, type: `%s'" (js2-node-type child)) + (setq pn (make-rjsx-wrapped-expr :pos beg :child child)) + (js2-node-add-children pn child) + (when (js2-error-node-p child) + (pop js2-parsed-errors)) ; We'll record our own message after checking for RC + (if (js2-match-token js2-RC) + (rjsx-maybe-message "matched } after expression") + (rjsx-maybe-message "did not match } after expression") + (when skip-to-rc + (while (not (memql (js2-get-token) (list js2-RC js2-EOF js2-DIV js2-GT))) + (rjsx-maybe-message "Skipped over `%s'" (js2-current-token-string))) + (when (memq (js2-current-token-type) (list js2-DIV js2-GT)) + (js2-unget-token))) + (unless (js2-error-node-p child) + (js2-report-error "msg.no.rc.after.expr" nil beg + (- (js2-current-token-beg) beg)))) + (when (js2-error-node-p child) + (js2-report-error "msg.syntax" nil beg (- (js2-current-token-end) beg))) + (setf (js2-node-len pn) (- (js2-current-token-end) beg)) + pn))) + +(defun rjsx-parse-string () + "Verify that current token is a valid JSX string. +Returns a `js2-error-node' if TOKEN-STRING is not a valid JSX +string, otherwise returns a `js2-string-node'. (Strings are +invalid if they contain the delimiting quote character inside)" + (rjsx-maybe-message "Parsing string") + (let* ((token (js2-current-token)) + (beg (js2-token-beg token)) + (len (- (js2-token-end token) beg)) + (token-string (js2-token-string token)) ;; JS2 does not include the quote-chars + (quote-char (char-before (js2-token-end token)))) + (if (cl-position quote-char token-string) + (progn + (js2-report-error "msg.invalid.jsx.string" nil beg len) + (make-js2-error-node :pos beg :len len)) + (make-js2-string-node :pos beg :len len :value token-string)))) + +(cl-defun rjsx-parse-identifier (&optional face &key (allow-ns t)) + "Parse a possibly namespaced identifier and fontify with FACE if given. +Returns a `js2-error-node' if unable to parse. If the &key +argument ALLOW-NS is nil, does not allow namespaced names." + (if (js2-must-match-name "msg.bad.jsx.ident") + (let ((pn (make-rjsx-identifier)) + (beg (js2-current-token-beg)) + (name-parts (list (js2-current-token-string))) + (allow-colon allow-ns) + (continue t) + (prev-token-end (js2-current-token-end)) + (name-start (js2-current-token-beg)) + matched-colon) + (while (and continue + (or (and (memq (js2-peek-token) (list js2-SUB js2-ASSIGN_SUB)) + (prog2 ; Ensure no whitespace between previous name and this dash + (js2-get-token) + (eq prev-token-end (js2-current-token-beg)) + (js2-unget-token))) + (and allow-colon (= (js2-peek-token) js2-COLON)))) + (if (setq matched-colon (js2-match-token js2-COLON)) + (setf (rjsx-identifier-namespace pn) (apply #'concat (nreverse name-parts)) + allow-colon nil + name-parts (list) + name-start nil) + (when (= (js2-get-token) js2-ASSIGN_SUB) ; Otherwise it's a js2-SUB + (setf (js2-token-end (js2-current-token)) (1- (js2-current-token-end)) + (js2-token-type (js2-current-token)) js2-SUB + (js2-token-string (js2-current-token)) "-" + js2-ts-cursor (1+ (js2-current-token-beg)) + js2-ti-lookahead 0)) + (push "-" name-parts)) + (setq prev-token-end (js2-current-token-end)) + (if (js2-match-token js2-NAME) + (if (eq prev-token-end (js2-current-token-beg)) + (progn (push (js2-current-token-string) name-parts) + (setq prev-token-end (js2-current-token-end) + name-start (or name-start (js2-current-token-beg)))) + (js2-unget-token) + (setq continue nil)) + (when (= js2-COLON (js2-current-token-type)) + (js2-report-error "msg.bad.jsx.ident" nil beg (- (js2-current-token-end) beg))) + ;; We only keep going if this is an `ident-ending-with-dash-colon:' + (setq continue (and (not matched-colon) (= (js2-peek-token) js2-COLON))))) + (when face + (js2-set-face beg (js2-current-token-end) face 'record)) + (let ((name-node (if name-start + (make-js2-name-node :pos name-start + :len (- (js2-current-token-end) name-start) + :name (apply #'concat (nreverse name-parts))) + (make-js2-name-node :pos (js2-current-token-end) :len 0 :name "")))) + (setf (js2-node-len pn) (- (js2-current-token-end) beg) + (rjsx-identifier-name pn) name-node) + (js2-node-add-children pn name-node)) + pn) + (make-js2-error-node :len (js2-current-token-len)))) + +(defun rjsx-parse-member-or-ns (&optional face) + "Parse a dotted expression or a namespaced identifier and fontify with FACE if given." + (let ((ident (rjsx-parse-identifier face))) + (cond + ((js2-error-node-p ident) ident) + ((rjsx-identifier-namespace ident) ident) + (t (rjsx-parse-member ident face))))) + +(defun rjsx-parse-member (ident &optional face) + "Parse a dotted member expression starting with IDENT and fontify with FACE. +IDENT is the `rjsx-identifier' node for the first item in the +member expression. Returns a `js2-error-node' if unable to +parse." + (let (idents dots-pos pn end) + (setq pn (make-rjsx-member :pos (js2-node-pos ident))) + (setq end (js2-current-token-end)) + (push ident idents) + (while (and (js2-match-token js2-DOT) (not (js2-error-node-p ident))) + (push (js2-current-token-beg) dots-pos) + (setq end (js2-current-token-end)) + (setq ident (rjsx-parse-identifier nil :allow-ns nil)) + (push ident idents) + (unless (js2-error-node-p ident) + (setq end (js2-current-token-end))) + (js2-node-add-children pn ident)) + (apply 'js2-node-add-children pn idents) + (setf (rjsx-member-idents pn) (nreverse idents) + (rjsx-member-dots-pos pn) (nreverse dots-pos) + (js2-node-len pn) (- end (js2-node-pos pn))) + (when face + (js2-set-face (js2-node-pos pn) end face 'record)) + pn)) + + +(defun rjsx-parse-child (expect-fragment) + "Parse an XML child node. +Child nodes include plain (unquoted) text, other XML elements, +and {}-bracketed expressions. Return the parsed child. + +EXPECT-FRAGMENT if t, indicates that `</>' should be parsed +as a fragment closing node, and not as an empty tag." + (let ((tt (rjsx-get-next-xml-token))) + (rjsx-maybe-message "child type `%s'" tt) + (cond + ((= tt js2-LT) + (rjsx-maybe-message "xml-or-close") + (rjsx-parse-xml-or-closing-tag expect-fragment)) + + ((= tt js2-LC) + (rjsx-maybe-message "parsing expression { %s" (js2-peek-token)) + (rjsx-parse-wrapped-expr t nil)) + + ((= tt rjsx-JSX-TEXT) + (rjsx-maybe-message "text node: '%s'" (js2-current-token-string)) + (js2-set-face (js2-current-token-beg) (js2-current-token-end) 'rjsx-text 'record) + (js2-record-text-property (js2-current-token-beg) (js2-current-token-end) + 'syntax-table rjsx-text-syntax-table) + (make-rjsx-text :value (js2-current-token-string))) + + ((= tt js2-ERROR) + (make-js2-error-node :len (js2-current-token-len))) + + (t (error "Unexpected token type: %s" (js2-peek-token)))))) + +(defun rjsx-parse-xml-or-closing-tag (expect-fragment) + "Parse a JSX tag, which could be a child or a closing tag. +Return the parsed child, which is a `rjsx-closing-tag' if a +closing tag was parsed. + +EXPECT-FRAGMENT if t, indicates that `</>' should be parsed +as a fragment closing node, and not as an empty tag." + (let ((beg (js2-current-token-beg)) pn) + (rjsx-record-token-class '<) + (rjsx-record-tag-bracket-face) + (if (and (not expect-fragment) (setq pn (rjsx-parse-empty-tag))) + pn + (if (js2-match-token js2-DIV) + (progn (rjsx-record-tag-bracket-face) + (if (and expect-fragment (eq (js2-peek-token) js2-GT)) + (setq pn (make-rjsx-closing-tag :pos beg)) + (setq pn (make-rjsx-closing-tag :pos beg + :name (rjsx-parse-member-or-ns 'rjsx-tag))) + (js2-node-add-children pn (rjsx-closing-tag-name pn))) + (when (js2-must-match js2-GT "msg.no.gt.in.closer" beg (- (js2-current-token-end) beg)) + (rjsx-record-token-class '>) + (rjsx-record-tag-bracket-face)) + (setf (js2-node-len pn) (- (js2-current-token-end) beg)) + pn) + (rjsx-maybe-message "parsing a child XML item") + (rjsx-parse-xml))))) + +(defun rjsx-get-next-xml-token () + "Scan through the XML text and push one token onto the stack." + (setq js2-ts-string-buffer nil) ; for recording the text + (when (> js2-ti-lookahead 0) + (setq js2-ts-cursor (js2-current-token-end)) + (setq js2-ti-lookahead 0)) + + (let ((token (js2-new-token 0)) + c) + (rjsx-maybe-message "Running the xml scanner") + (catch 'return + (while t + (setq c (js2-get-char)) + (rjsx-maybe-message "'%s' (%s)" (if (= c js2-EOF_CHAR) "EOF" (char-to-string c)) c) + (cond + ((or (= c ?}) (= c ?>)) + (js2-set-string-from-buffer token) + (setf (js2-token-type token) js2-ERROR) + (js2-report-scan-error "msg.syntax" t) + (throw 'return js2-ERROR)) + + ((or (= c ?<) (= c ?{)) + (js2-unget-char) + (if js2-ts-string-buffer + (progn + (js2-set-string-from-buffer token) + (setf (js2-token-type token) rjsx-JSX-TEXT) + (rjsx-maybe-message "created rjsx-JSX-TEXT token: `%s'" (js2-token-string token)) + (throw 'return rjsx-JSX-TEXT)) + (js2-get-char) + (js2-set-string-from-buffer token) + (setf (js2-token-type token) (if (= c ?<) js2-LT js2-LC)) + (setf (js2-token-string token) (string c)) + (throw 'return (js2-token-type token)))) + + ((= c js2-EOF_CHAR) + (js2-set-string-from-buffer token) + (rjsx-maybe-message "Hit EOF. Current buffer: `%s'" (js2-token-string token)) + (setf (js2-token-type token) js2-ERROR) + (rjsx-maybe-message "Scanner hit EOF. Panic!") + (signal 'rjsx-eof-while-parsing nil)) + (t (js2-add-to-string c))))))) + +(js2-deflocal rjsx-buffer-chars-modified-tick 0 "Variable holding the last per-buffer value of `buffer-chars-modified-tick'.") + +(defun rjsx-maybe-reparse () + "Called before accessing the parse tree. +For small buffers, will do an immediate reparse to ensure the +parse tree is up to date." + (when (and (<= (point-max) rjsx-max-size-for-frequent-reparse) + (/= rjsx-buffer-chars-modified-tick (buffer-chars-modified-tick))) + (js2-reparse) + (setq rjsx-buffer-chars-modified-tick (buffer-chars-modified-tick)))) + +(defun rjsx--tag-at-point () + "Return the JSX tag at point, if any, or nil." + (rjsx-maybe-reparse) + (let ((node (js2-node-at-point (point) t))) + (while (and node (not (rjsx-node-p node))) + (setq node (js2-node-parent node))) + node)) + + +;;;; Interactive commands and keybindings +(defun rjsx-electric-lt (n) + "Insert a context-sensitive less-than sign. +Optional prefix argument N indicates how many signs to insert. +If N is greater than one, no special handling takes place. +Otherwise, if the less-than sign would start a JSX block, it +inserts `</>' and places the cursor inside the new tag." + (interactive "p") + (if (/= n 1) + (insert (make-string n ?<)) + (if (save-excursion + (forward-comment most-negative-fixnum) + (skip-chars-backward "\n\r") + (or (= (point) (point-min)) + (memq (char-before) (append "=(?:>}&|{," nil)) + (let ((start (- (point) 6))) + (and (>= start (point-min)) + (string= (buffer-substring start (point)) "return"))))) + (progn (insert "</>") + (backward-char 2)) + (insert "<")))) + +(define-key rjsx-mode-map "<" 'rjsx-electric-lt) + +(defun rjsx-expand-self-closing-tag (node) + "Expand NODE into a balanced tag. +Assumes NODE is self-closing `rjsx-node', and that point is at +the self-closing slash." + (delete-char 1) + (search-forward ">") + (save-excursion + (insert "</" (rjsx-node-opening-tag-name node) ">"))) + +(defun rjsx-electric-gt (n) + "Insert a context-sensitive greater-than sign. +Optional prefix argument N indicates how many signs to insert. +If N is greater than one, no special handling takes place. +Otherwise, if point is in a self-closing JSX tag just before the +slash, it creates a matching end-tag and places point just inside +the tags." + (interactive "p") + (if (or (/= n 1) + (not (eq (get-char-property (point) 'rjsx-class) 'self-closing-slash))) + (insert (make-string n ?>)) + (let ((node (rjsx--tag-at-point))) + (if node + (rjsx-expand-self-closing-tag node) + (insert (make-string n ?>)))))) + +(define-key rjsx-mode-map ">" 'rjsx-electric-gt) + +(defun rjsx-delete-creates-full-tag (n &optional killflag) + "N and KILLFLAG are as in `delete-char'. +If N is 1 and KILLFLAG nil, checks to see if we're in a +self-closing tag about to delete the slash. If so, deletes the +slash and inserts a matching end-tag." + (interactive "p") + (if (or killflag (/= 1 n) (not (eq (get-char-property (point) 'rjsx-class) 'self-closing-slash))) + (if (called-interactively-p 'any) + (call-interactively 'delete-forward-char) + (delete-char n killflag)) + (let ((node (rjsx--tag-at-point))) + (if node + (rjsx-expand-self-closing-tag node) + (delete-char 1))))) + +(define-key rjsx-mode-map (kbd "C-d") 'rjsx-delete-creates-full-tag) + +(defun rjsx-rename-tag-at-point (new-name) + "Prompt for a new name and modify the tag at point. +NEW-NAME is the name to give the tag." + (interactive "sNew tag name: ") + (let* ((tag (rjsx--tag-at-point)) + (closer (and tag (rjsx-node-closing-tag tag)))) + (cond + ((null tag) (message "No JSX tag found at point")) + ((null (rjsx-node-name tag)) ; fragment + (cl-assert closer nil "Fragment has no closing-tag") + (save-excursion + (goto-char (+ 2 (js2-node-abs-pos closer))) + (insert new-name) + (goto-char (1+ (js2-node-abs-pos tag))) + (insert new-name)) + (js2-reparse)) + (t + (let* ((head (rjsx-node-name tag)) + (tail (when closer (rjsx-closing-tag-name closer))) + beg end) + (dolist (part (if tail (list tail head) (list head))) + (setq beg (js2-node-abs-pos part) + end (+ beg (js2-node-len part))) + (delete-region beg end) + (save-excursion (goto-char beg) (insert new-name))) + (js2-reparse)))))) + +(define-key rjsx-mode-map (kbd "C-c C-r") 'rjsx-rename-tag-at-point) + + + +;; Utilities + +(defun rjsx-ancestor (node predicate) + "Find an ancestor of NODE satisfying PREDICATE. + +Search upwards from the parent of NODE for an ancestor where +PREDICATE returns t. Returns nil if no ancestor satisfies +PREDICATE." + (let ((ancestor (js2-node-parent node))) + (while (and ancestor + (not (funcall predicate ancestor))) + (setq ancestor (js2-node-parent ancestor))) + ancestor)) + +(cl-defun rjsx--prev-sibling (node) + "Get the previous non-blank sibling of NODE." + (let* ((parent (js2-node-parent node)) prev) + (dolist (kid (rjsx-node-kids parent)) + (cond + ((eq kid node) (cl-return-from rjsx--prev-sibling prev)) + ((and (rjsx-text-p kid) (string-blank-p (rjsx-text-value kid)))) + (t (setq prev kid)))))) + + + +;; Comment handling + +(defun rjsx-comment-region-function (beg end &optional arg) + (js2-mode-wait-for-parse + (lambda () + (let* ((node (js2-node-at-point beg)) + (in-jsx (or (rjsx-node-p node) + (rjsx-ancestor node 'rjsx-node-p))) + (use-jsx-comment (and (rjsx-node-p (js2-node-parent node)) + (or (rjsx-text-p node) + (and (rjsx-node-p node) + (= (js2-node-abs-pos node) beg)))))) + (cond (use-jsx-comment + (let ((comment-start "{/*") + (comment-end "*/}")) + (comment-normalize-vars) + (comment-region-default beg end arg))) + (in-jsx + (let ((comment-start "/*") + (comment-end "*/")) + (comment-normalize-vars) + (if (rjsx-wrapped-expr-p node) + (if (js2-empty-expr-node-p (rjsx-wrapped-expr-child node)) + (let ((comment-start "{/*") + (comment-end "*/}")) + (comment-normalize-vars) + (comment-region-default beg end arg)) + (comment-region-default (1+ beg) (1- end) arg)) + (comment-region-default beg end arg)))) + (t (comment-region-default beg end arg))))))) + +(defun rjsx-maybe-unwrap-expr (beg end) + (save-excursion + (save-restriction + (js2-reparse) + (goto-char beg) + (skip-chars-forward "[:space:]\n" end) + (let* ((node (js2-node-at-point (point))) + (parent (js2-node-parent node))) + (when (and parent + (rjsx-wrapped-expr-p parent) + (rjsx-node-p (js2-node-parent parent)) + (or (rjsx-text-p node) (rjsx-node-p node))) + (let* ((expr-start (js2-node-abs-pos parent)) + (expr-end (+ expr-start (js2-node-len parent))) + (body-start (1+ expr-start)) + (body-end (1- expr-end)) + (body-length (- body-end body-start))) + (when (and (comment-only-p body-start beg) + (comment-only-p end body-end)) + (goto-char expr-start) + (delete-char 1) + (forward-char body-length) + (delete-char 1)))))))) + +(defun rjsx-uncomment-region-function (beg end &optional _) + (js2-mode-wait-for-parse + (lambda () + (goto-char beg) + (setq end (copy-marker end)) + (let (cs ts te ce matched-start) + ;; find comment start + (while (and (<= (point) end) + (setq matched-start + (and (re-search-forward comment-start-skip end t 1) + (match-string-no-properties 0)))) + ;; delete comment-start + (setq cs (match-beginning 1)) + (setq ts (match-end 1)) + (goto-char cs) + (delete-region cs ts) + + ;; delete comment-padding start + (when (and comment-padding (looking-at (regexp-quote comment-padding))) + (delete-region (point) (+ (point) (length comment-padding)))) + + ;; find comment end + (when (re-search-forward (if (string-match "//+" matched-start) "\n" "\\*/}?") end t 1) + (setq te (or (match-beginning 1) (match-beginning 0))) + (setq ce (or (match-end 1) (match-end 0))) + (goto-char te) + + ;; delete commend-end if it's not a newline + (unless (string= "\n" (match-string-no-properties 0)) + (delete-region te ce) + + ;; delete comment-padding end + (when comment-padding + (backward-char (length comment-padding)) + (when (looking-at (regexp-quote comment-padding)) + (delete-region (point) (+ (point) (length comment-padding)))))) + + ;; unescape inner comments if any + (save-restriction + (narrow-to-region cs (point)) + (comment-quote-nested "{/*" "*/}" t))))) + + (rjsx-maybe-unwrap-expr beg end) + + (set-marker end nil)))) + +(defun rjsx-comment-quote-nested-function (_ __ unp) + (let ((re (concat "\\*\\(\\\\" (if unp "+" "*") "\\)/}?" + "\\|" + "{?/\\(\\\\" (if unp "+" "*") "\\)\\*"))) + (goto-char (point-min)) + (while (re-search-forward re (point-max) t 1) + (let ((ceme (match-end 1)) + (csme (match-end 2))) + (goto-char (or ceme csme)) + (if (and unp (>= (length (or (match-string-no-properties 1) + (match-string-no-properties 2))) + 1)) + (delete-char -1) + (insert "\\")))))) + +;;;###autoload +(defun rjsx-comment-dwim (arg) + "RJSX implementation of `comment-dwim'. If called on a region, +this function delegates to `comment-or-uncomment-region'. If the +point is not in a JSX context, it delegates to the +`comment-dwim', otherwise it will comment the JSX AST node at +point using the apppriate comment delimiters. + +For example: If point is on a JSX attribute or JSX expression, it +will comment the entire attribute using \"/* */\". , otherwise if +it's on a descendent JSX Element, it will use \"{/* */}\" +instead." + (interactive "*P") + (js2-mode-wait-for-parse + (lambda () + (if (use-region-p) + (comment-or-uncomment-region (region-beginning) (region-end) arg) + (save-excursion + (when (looking-at "[[:space:]]") + (forward-whitespace 1)) + (let ((node (js2-node-at-point))) + (cond + ;; If inside a regular JS comment node, uncomment the node + ((js2-comment-at-point) + (uncomment-region (js2-node-abs-pos node) (js2-node-abs-end node))) + ;; If looking at a commented JSXAttribute value, forward one char to + ;; uncomment the body + ((and (looking-at comment-start-skip) + (looking-at "{") + (rjsx-attr-p (js2-node-parent node))) + (forward-char 1) + (rjsx-comment-dwim arg)) + ;; If the entire line is a comment, uncomment it + ((and (comment-only-p (point) (line-end-position)) + (not (looking-at "[[:space:]]*$"))) + (uncomment-region (point) (line-end-position))) + ;; If looking at JSXText, comment the current line + ((rjsx-text-p node) + (let ((comment-start "{/*") + (comment-end "*/}")) + (comment-line 1))) + ;; If looking at a JSXAttribute or a JSXSpreadAttribute, comment the + ;; entire attribute with C-style comment + ((or (rjsx-spread-p node) + (rjsx-ancestor node 'rjsx-spread-p) + (rjsx-attr-p node) + (and (js2-name-node-p node) + (rjsx-identifier-p (js2-node-parent node)) + (rjsx-attr-p (js2-node-parent (js2-node-parent node)))) + (and (rjsx-identifier-p node) + (rjsx-attr-p (js2-node-parent node))) + (and (js2-string-node-p node) + (rjsx-attr-p (js2-node-parent node)))) + (let ((node (or (and (rjsx-spread-p node) node) + (rjsx-ancestor node 'rjsx-spread-p) + (and (rjsx-attr-p node) node) + (rjsx-ancestor node 'rjsx-attr-p))) + (comment-start "/*") + (comment-end "*/")) + (comment-region + (js2-node-abs-pos node) + (js2-node-abs-end node) arg))) + ;; If looking at a JSXElement or JSXFragment, comment the entire + ;; node with JSX comment if it's a child of one of the above, + ;; otherwise just comment with C-style comment. + ((or (rjsx-node-p node) + (rjsx-closing-tag-p node) + (rjsx-member-p node) + (rjsx-ancestor node 'rjsx-member-p)) + (let* ((node (or (and (rjsx-node-p node) node) + (rjsx-ancestor node 'rjsx-node-p))) + (parent-node-p (rjsx-node-p (js2-node-parent node))) + (closing (rjsx-node-closing-tag node)) + (comment-start (if parent-node-p "{/*" "/*")) + (comment-end (if parent-node-p "*/}" "*/"))) + (comment-region + (js2-node-abs-pos node) + (js2-node-abs-end (or closing node)) arg))) + ;; If looking at a JSX {expression} or is inside a JSX expression, + ;; comment the body with regular C-style comment. If the body is + ;; already commented, uncomment it. If on a multi line JSX + ;; expression, just comment the line. + ((or (rjsx-wrapped-expr-p node) + (rjsx-ancestor node 'rjsx-wrapped-expr-p)) + (let* ((expr (or (and (rjsx-wrapped-expr-p node) node) + (rjsx-ancestor node 'rjsx-wrapped-expr-p))) + ;; Can only happen as a child of an element or fragment, an + ;; empty JSX expression attribute value will result in an + ;; error node + (expr-empty (js2-empty-expr-node-p (rjsx-wrapped-expr-child expr))) + (body-start (1+ (js2-node-abs-pos expr))) + (body-end (1- (js2-node-abs-end expr))) + (expr-child-of-node (rjsx-node-p (js2-node-parent expr)))) + ;; If the body is all comment, uncomment it, otherwise if it's + ;; empty, wrap it with a JSX comment + (if (and expr-child-of-node expr-empty) + (if (or (= (1+ (js2-node-abs-pos expr)) (js2-node-abs-end expr)) ;; {} + (string-blank-p (buffer-substring-no-properties body-start body-end))) + (let ((comment-start "{/*") + (comment-end "*/}")) + (comment-region (js2-node-abs-pos expr) (js2-node-abs-end expr) arg)) + (when (comment-only-p body-start body-end) + (uncomment-region body-start body-end arg))) + ;; If in a multi-line JSX expression, comment the line + (if (> (count-lines body-start body-end) 1) + (comment-dwim arg) + ;; Otherwise comment the entire body + (let ((comment-start "/*") + (comment-end "*/")) + (comment-region body-start body-end arg)))))) + ;; Otherwise just delegate to (comment-dwim) + (t (comment-dwim arg))))))))) + +(define-key rjsx-mode-map [remap comment-dwim] 'rjsx-comment-dwim) + + + +;; Indentation + +(defun rjsx--js--looking-at-operator-p-advice (orig-fun) + "Advice for `js--looking-at-operator-p' (ORIG-FUN) to handle JSX properly." + (if (memq (get-char-property (point) 'rjsx-class) '(< >)) + nil + (apply orig-fun nil))) + +(advice-add 'js--looking-at-operator-p :around #'rjsx--js--looking-at-operator-p-advice) + +(defvar-local rjsx--indent-region-p nil + "t when `indent-region' is running.") + +(defvar-local rjsx--indent-running-offset 0 + "Running offset to add to the node POS at the beginning of +every line during `indent-region' so we don't have to reparse +after indenting every line to update the AST node positions.") + +(defvar-local rjsx--node-abs-pos-cache nil + "Cache for JSX nodes' indent levels. +Used during `indent-region' calls to avoid repeated +`js2-node-abs-pos' calls.") + +(defun rjsx--node-abs-pos (node) + "Caching version of `js2-node-abs-pos' for NODE." + (if (null rjsx--node-abs-pos-cache) + (js2-node-abs-pos node) + (let ((cached (gethash node rjsx--node-abs-pos-cache))) + (or cached + (setf (gethash node rjsx--node-abs-pos-cache) + (js2-node-abs-pos node)))))) + +(defsubst rjsx--node-indent-level (node) + "Return the indent level for NODE." + (save-excursion (goto-char (rjsx--node-abs-pos node)) (current-column))) + +(defsubst rjsx--indent-line-to-offset (node offset) + "Indent current line relative to NODE, which must be an `rjsx-node' instance. +OFFSET indicates the number of spaces to add." + (indent-line-to + (+ offset (rjsx--node-indent-level node)))) + +(defun rjsx-indent-line () + "Similar to `js-jsx-indent-line', but fixes indentation bug +with JSXElement after a JSX expression and arrow function. In +addition, the > of a multi-line open tag and the /> of a +multi-line self-closing tag are aligned to the beginning <. This +function also ensures everything inside a JSX context is indented +according to `js-indent-level' using spaces, this is due to the +limitation of `sgml-indent-line' not being able to indent with +tabs. + +Fixes: +- https://github.com/mooz/js2-mode/issues/490 +- https://github.com/mooz/js2-mode/issues/482 +- https://github.com/mooz/js2-mode/issues/462 +- https://github.com/mooz/js2-mode/issues/451 +" + (unless rjsx--indent-region-p + (js2-reparse)) + + (let* ((indent-tabs-mode nil) + (cur-pos (save-excursion (back-to-indentation) (point))) + (cur-char (char-after cur-pos)) + (node (js2-node-at-point (- cur-pos rjsx--indent-running-offset))) + (parent (js2-node-parent node))) + (cond + ((rjsx-node-p node) + (cond + ((eq cur-char ?<) + (if (rjsx-node-p parent) + (rjsx--indent-line-to-offset parent sgml-basic-offset) + ;; Top-level node, indent as JS + (js-indent-line)) + (when rjsx--node-abs-pos-cache + (setf (gethash node rjsx--node-abs-pos-cache) + (save-excursion (back-to-indentation) (point))))) + ((memq cur-char '(?/ ?>)) + (rjsx--indent-line-to-offset node 0)) + ((eq cur-char ?\n) + (rjsx--indent-line-to-offset node sgml-basic-offset)) + (t (error "Don't know how to indent %s for JSX node" (make-string 1 cur-char))))) + ((and (rjsx-identifier-p parent) + (rjsx-member-p (js2-node-parent parent)) + (rjsx-node-p (js2-node-parent (js2-node-parent parent)))) + (rjsx--indent-line-to-offset (js2-node-parent (js2-node-parent parent)) 0)) + + ;; JSX children + ((rjsx-closing-tag-p node) + (rjsx--indent-line-to-offset parent 0)) + ((rjsx-text-p node) + (rjsx--indent-line-to-offset parent sgml-basic-offset)) + ((rjsx-wrapped-expr-p node) + (if (eq cur-char ?}) + (js-indent-line) + (rjsx--indent-line-to-offset parent sgml-basic-offset))) + + ;; Attribute-like (spreads, attributes, etc.) + ;; if first attr is on same line as tag, then align + ;; otherwise indent to parent level + sgml-basic-offset + ((or (rjsx-identifier-p node) + (and (rjsx-identifier-p parent) + (rjsx-attr-p (js2-node-parent parent))) + (rjsx-spread-p node)) + (let* ((tag (or (rjsx-ancestor node #'rjsx-node-p) + (error "Did not find containing JSX tag for attributes"))) + (name (rjsx-node-name tag)) + column) + (save-excursion + (goto-char (rjsx--node-abs-pos tag)) + (setq column (current-column)) + (when name (forward-char (js2-node-end name)) (skip-chars-forward " \t")) + (if (eolp) + (setq column (+ column sgml-basic-offset sgml-attribute-offset)) + (setq column (current-column)))) + (indent-line-to column))) + + ;; Everything else indent as javascript + (t (js-indent-line))) + + (when rjsx--indent-region-p + (cl-incf rjsx--indent-running-offset + (- (save-excursion (back-to-indentation) (point)) + cur-pos))))) + +(defun rjsx-indent-region (start end) + "Indent the region from START to END as JS/JSX." + (js2-reparse) + (let ((rjsx--indent-region-p t) + (rjsx--indent-running-offset 0) + (rjsx--node-abs-pos-cache (make-hash-table))) + (js2-indent-region start end))) + + +(provide 'rjsx-mode) +;;; rjsx-mode.el ends here + +;; Local Variables: +;; outline-regexp: ";;;\\(;* [^ +;; ]\\|###autoload\\)\\|(....." +;; End: diff --git a/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode.elc b/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode.elc new file mode 100644 index 000000000000..6d39dc917bc8 --- /dev/null +++ b/configs/shared/emacs/.emacs.d/elpa/rjsx-mode-20180823.1645/rjsx-mode.elc Binary files differ |