├── .gitattributes ├── .gitignore ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── LICENSE ├── Makefile ├── README.md ├── demo.png ├── js2-parser.md ├── js2-tests.el ├── key-demo.gif ├── rjsx-mode.el └── rjsx-tests.el /.gitattributes: -------------------------------------------------------------------------------- 1 | *.png binary 2 | *.jpeg binary 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled 2 | *.elc 3 | 4 | # Packaging 5 | .cask 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This config was adapted from flycheck/emacs-travis 2 | # Copyright © 2015 Sebastian Wiesner swiesner@lunaryorn.com 3 | 4 | # Permission is hereby granted, free of charge, to any person 5 | # obtaining a copy of this software and associated documentation files 6 | # (the "Software"), to deal in the Software without restriction, 7 | # including without limitation the rights to use, copy, modify, merge, 8 | # publish, distribute, sublicense, and/or sell copies of the Software, 9 | # and to permit persons to whom the Software is furnished to do so, 10 | # subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be 13 | # included in all copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 | # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | language: emacs-lisp 25 | sudo: required 26 | dist: trusty 27 | group: deprecated-2017Q4 28 | cache: 29 | - directories: 30 | # Cache stable Emacs binaries (saves 1min per job) 31 | - "$HOME/emacs/" 32 | # Allow Emacs snapshot builds to fail and don’t wait for these as they can take 33 | # a looooong time 34 | matrix: 35 | fast_finish: true 36 | allow_failures: 37 | - env: EMACS_VERSION=snapshot 38 | env: 39 | - EMACS_VERSION=24.5 40 | - EMACS_VERSION=25.3 41 | - EMACS_VERSION=26.1 42 | - EMACS_VERSION=snapshot 43 | before_install: 44 | # Configure $PATH: Executables are installed to $HOME/bin 45 | - export PATH="$HOME/bin:$PATH" 46 | # Download the makefile to emacs-travis.mk 47 | - wget 'https://raw.githubusercontent.com/flycheck/emacs-travis/master/emacs-travis.mk' 48 | # Install Emacs (according to $EMACS_VERSION) and Cask 49 | - make -f emacs-travis.mk install_emacs 50 | install: 51 | - make install-deps 52 | script: 53 | - make test EMACS=${EMACS} 54 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please check if the bug still exists when using `js2-jsx-mode` (if 2 | applicable). If so, it is most likely an upstream bug and should be 3 | reported there. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Felipe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EMACS = emacs 2 | 3 | BATCHFLAGS = -batch -Q 4 | 5 | SRCS = rjsx-mode.el 6 | OBJS = rjsx-mode.elc 7 | 8 | JS2_LINK = https://raw.githubusercontent.com/mooz/js2-mode/master/js2-mode.el 9 | JS2_OLD_INDENT_LINK = https://raw.githubusercontent.com/mooz/js2-mode/master/js2-old-indent.el 10 | 11 | 12 | %.elc: %.el 13 | ${EMACS} $(BATCHFLAGS) -L . -f batch-byte-compile $^ 14 | 15 | all: $(OBJS) 16 | 17 | clean: 18 | -rm -f $(OBJS) 19 | 20 | test: 21 | ${EMACS} $(BATCHFLAGS) -L . -l rjsx-mode.el -l js2-tests.el -l rjsx-tests.el\ 22 | -f ert-run-tests-batch-and-exit 23 | 24 | install-deps: 25 | curl --silent '$(JS2_LINK)' > js2-mode.el 26 | curl --silent '$(JS2_OLD_INDENT_LINK)' > js2-old-indent.el 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rjsx-mode: A major mode for editing JSX files 2 | [![MELPA](https://melpa.org/packages/rjsx-mode-badge.svg)](https://melpa.org/#/rjsx-mode) 3 | [![MELPA Stable](https://stable.melpa.org/packages/rjsx-mode-badge.svg)](https://stable.melpa.org/#/rjsx-mode) 4 | [![Build Status](https://travis-ci.org/felipeochoa/rjsx-mode.svg?branch=master)](https://travis-ci.org/felipeochoa/rjsx-mode) 5 | 6 | This mode derives from `js2-mode`, extending its parser to support JSX syntax 7 | according to the [official spec](https://facebook.github.io/jsx/). This 8 | means you get all of the `js2` features plus proper syntax checking 9 | and highlighting of JSX code blocks. 10 | 11 | Here's a screenshot of it in action: 12 | 13 | Actual syntax highlighting and no spurious errors! 14 | 15 | 16 | ## Installing 17 | 18 | `rjsx-mode` is available on [Melpa](https://melpa.org/), so if you have that 19 | repository configured you can just `package-list-packages` and install it from there. 20 | (If not, you can follow [their guide](https://melpa.org/#/getting-started) on 21 | getting started). `rjsx-mode` automatically registers itself for `*.jsx` files, 22 | but you can use `(add-to-list 'auto-mode-alist '("components\\/.*\\.js\\'" . rjsx-mode))` 23 | 24 | Alternatively, you can download `rjsx-mode.el` from this repository and use 25 | `load-file` or similar to add it to your current session. 26 | 27 | ## Features 28 | 29 | `js2-mode` does not include a JSX parser, but rather an E4X parser, which 30 | means it gets confused with certain JSX constructs. This mode extends the 31 | `js2** parser to support all JSX constructs and proper syntax highlighting 32 | and **indentation**. 33 | 34 | `rjsx-mode` adds some electricity to `<` and `C-d` to make adding new 35 | JSX elements less repetitive: 36 | 37 | * `<` inserts `` whenever it would start a new JSX node (and simply 38 | inserts `<` otherwise) 39 | * `>` or `C-d` right before the slash in a self-closing tag 40 | automatically inserts a closing tag and places point inside the 41 | element 42 | 43 | The result is you can do the following: 44 | 45 | Quickly and easily add new components 46 | 47 | * `C-c C-r` lets you rename the enclosing tag 48 | 49 | Some features that this mode adds to `js2`: 50 | 51 | * Proper indentation of JSX regardless of how you write it. (No need to wrap 52 | in parentheses!) 53 | * Highlighting JSX tag names and attributes (using the `rjsx-tag` and 54 | `rjsx-attr` faces) 55 | * Parsing the spread operator `{...otherProps}` 56 | * `&&` and `||` in child expressions `
{cond && }
` 57 | * Ternary expressions `
{toggle ? : }
` 59 | 60 | If you don't like this behavior, you can disable it by adding the following to 61 | your init file: 62 | 63 | ```elisp 64 | (with-eval-after-load 'rjsx-mode 65 | (define-key rjsx-mode-map "<" nil) 66 | (define-key rjsx-mode-map (kbd "C-d") nil) 67 | (define-key rjsx-mode-map ">" nil)) 68 | ``` 69 | 70 | Additionally, since `rjsx-mode` extends the `js2` AST, utilities using the 71 | parse tree gain access to the JSX structure. 72 | 73 | ### Indentation 74 | 75 | `rjsx-mode` extends the built-in javascript indentation engine to correctly 76 | support JSX. You can configue the depth of indentation using `js-indent-level` 77 | and `sgml-basic-offset`, along with the various `js-indent-` variables. 78 | 79 | **Indenting with tabs**: This is not currently supported. You can either submit 80 | a PR if interested (look at issue #85) or revert to using the built-in 81 | indentation mode by adding `(setq-local indent-line-function 82 | 'js-jsx-indent-line)` to your `rjsx-mode-hook`. 83 | 84 | ## Bugs, contributing 85 | 86 | Please submit any bugs or feature requests on the GitHub tracker. Since this 87 | mode is based on `js2`, it is possible that bugs you encounter with it stem from 88 | there. Please try reproducing bugs using `js2-mode` if relevant. If the bug 89 | is in `js2`, please report it using `M-x report-emacs-bug`. 90 | 91 | ## License 92 | 93 | This project is licensed under the MIT license. 94 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipeochoa/rjsx-mode/b697fe4d92cc84fa99a7bcb476f815935ea0d919/demo.png -------------------------------------------------------------------------------- /js2-parser.md: -------------------------------------------------------------------------------- 1 | # Notes on the js2-mode parser 2 | 3 | 4 | * Initialized/called by `js2-parse` 5 | * Only one active scanner/token stream per buffer 6 | 7 | * The lexer (aka scanner in the docs) runs JIT before the parser 8 | itself 9 | * State is managed using buffer-local variables. The main variables 10 | that the scanner/parsers use are: 11 | 12 | - `js2-ti-tokens`: Vector of recently lexed (not necessarily parsed) 13 | - `js2-ti-tokens-cursor`: Pointer into `js2-ti-tokens` with the 14 | "current" token 15 | - `js2-ti-lookahead`: Counter recording how many tokens have been 16 | lexed but not parsed 17 | 18 | `js2-get-token` is used to advance the current token cursor, which 19 | automatically pulls in more tokens if `js2-ti-tokens` is out 20 | 21 | ## Lexer/Scanner: 22 | 23 | * `js2-get-token` is the function called by most of the token-dealing 24 | code. It looks in the buffer, and dispatches to 25 | `js2-get-token-internal` if it needs to read a new token 26 | * `js2-get-token-internal` calls the scanner (using 27 | `js2-get-token-internal-1`), processes comments, and fontifies 28 | errors and keywords 29 | 30 | 31 | ## `js2-parse` 32 | 33 | 1. initializes (buffer-local) state variables 34 | 3. calls `js2-do-parse` 35 | 4. does error reporting 36 | 5. runs post-parse hooks 37 | 6. returns AST from `js2-do-parse` 38 | 39 | 40 | ## `js2-do-parse` 41 | 42 | This function creates the ast instance and runs a while loop that 43 | parses all of the top-level blocks. Functions are parsed using 44 | `js2-parse-function-stmt` (called after consuming the "function" 45 | keyword) and other statements are called using `js2-parse-statement`. 46 | 47 | There's also the "use strict" handling here and some comment-handling 48 | stuff. At the end, post-parse-callbacks are run and undeclared-vars 49 | are highlighted. 50 | 51 | Local variables: 52 | 53 | * **root**: `(make-js2-ast-root :buffer (buffer-name) :pos pos)` 54 | * **tt**: `(js2-get-token)` 55 | * **n**: One of `(js2-parse-function-expr)` or `(js2-parse-function-stmt)` 56 | 57 | 58 | ## `js2-parse-function-stmt` 59 | 60 | * Checks for a generator function `star-p` 61 | * Ensure function is not unnamed `(js2-must-match-name "msg.unnamed.function.stmt")` 62 | * Parses out the Rhino-specific "member expression", if any 63 | * Parses out the left paren 64 | * Calls `(js2-parse-function 'FUNCTION_STATEMENT pos star-p async-p name)` and returns its 65 | value. 66 | 67 | 68 | ## `js2-parse-function` 69 | 70 | Calls `js2-parse-function-internal` and handles re-parsing of the function if strict 71 | directives are found 72 | 73 | ## `js2-parse-function-internal` 74 | 75 | 1. Binds its name (if it's a statement) in the parent scope 76 | 2. Creates new scope (incl. handling dynamic scope) 77 | 3. Parses function params using `js2-parse-function-params` 78 | 4. Checks for arrow syntax 79 | 5. Dispatch body parsing based on whether there are curly braces (deprecated SpiderMonkey 80 | feature) --> mostly use `(js2-parse-function-body fn-node)` 81 | 6. Check for inconsistent returns 82 | 7. Binds its name (maybe) in its own scope 83 | 84 | 85 | ## `js2-parse-function-body` 86 | 87 | 1. Creates a new block node 88 | 2. Pushes statement nodes `(js2-parse-statement)` onto the block node 89 | 3. Check for use strict directive and throw 'reparse t if found 90 | 91 | ## `js2-parse-statement` 92 | 93 | * Checks for user input to interrupt 94 | * Checks for no-side-effects code 95 | * Parses statements using `js2-statement-helper` 96 | * Dynamic dispatch based on token type using `(aref js2-parsers tt)` 97 | 98 | ## `js2-parse-const-var` 99 | 100 | * Parses variable declarations and wraps it ina statement node using 101 | `make-js2-expr-stmt-node` 102 | * Calls `js2-parse-variables` for the actual parsing 103 | 104 | ## `js2-parse-variables` 105 | 106 | * Handles destructuring, name assignment in scope, declaration checks 107 | * Uses `js2-parse-destruct-primary-expr` for destructuring assignments 108 | * Uses `js2-parse-assign-expr` for the assignment expressions (i.e., the RHS) 109 | 110 | ## `js2-parse-assign-expr` 111 | 112 | * For yield expressions calls `js2-parse-return-or-yield` 113 | * For assignment (incl. augmented) calls itself recursively and ensures proper identifiers 114 | * For arrow functions, calls `js2-parse-function` (detecting whether it's async or not) 115 | * Everything else is handled through `js2-parse-cond-expr` 116 | 117 | ## `js2-parse-cond-expr` 118 | 119 | Uses `js2-parse-or-expr` to parse out one expression, then checks for the "? __ : __" 120 | pattern to create a cond-node 121 | 122 | ## `js2-parse-or-expr`, `js2-parse-and-expr`, `js2-parse-bit-or-expr`, ... 123 | 124 | Classic recursive descent parser. In order of priority: 125 | 126 | 1. OR 127 | 2. AND 128 | 3. BIT OR 129 | 4. BIT XOR 130 | 5. BIT AND 131 | 6. EQ [EQ, NE, SHEQ, SHNE] 132 | 7. REL [IN, INSTANCEOF, LE, LT, GE, GT] 133 | 8. SHIFT [LSH, URSH, RSH] 134 | 9. ADD/SUB 135 | 10. MUL/DIV/MOD 136 | 11. EXPON [incl. fix for right-associativity] 137 | 12. UNARY [VOID, NOT, BITNOT, TYPEOF, POS, NEG, INC, DEC, DELPROP] 138 | 139 | Unary is a more complicated parser, which checks for valid increment/decrement sequences, 140 | that delprop ("delete a.b") is called on a valid property, etc. It also dispatches parsing 141 | to `(js2-parse-member-expr-tail t (js2-parse-xml-initializer))` if it finds an XML 142 | snippet, and finally dispatches to `(js2-parse-member-expr t)` 143 | 144 | ## `js2-parse-member-expr` 145 | 146 | * Handles calls using "new" directly, or dispatches to `js2-parse-primary-expr` 147 | 148 | ## `js2-parse-primary-expr` 149 | 150 | Parse a literal (leaf) expression of some sort: 151 | 152 | * class 153 | * function/async function 154 | * Array literals or comprehensions 155 | * Object literal 156 | * Let expression 157 | * Parenthetisized or generator comp 158 | * XMLATTR [E4X attribute expression using @ sign, e.g., `@attr`] 159 | * names 160 | * numbers 161 | * strings 162 | * template literals 163 | * regexp literal 164 | * null, this, super, false, true 165 | * tripledot (must be arrow function in rest param) 166 | * reserved keyword 167 | * EOF 168 | * scanner error 169 | 170 | ## `js2-parse-xml-initializer` 171 | 172 | Parses a literal XML fragment. The tokenizer changes here to `js2-get-next-xml-token`, 173 | which emits the following token types: 174 | 175 | * js2-ERROR 176 | * js2-XML: Generic XML fragment, including CDATA, comments, doctype. Stops at any bare `{` 177 | * js2-XMLEND: Found the last close-tag 178 | 179 | The tokenizer tracks significantly more state than it emits (in-tag, in-attr, 180 | num-open-tags, etc.) 181 | 182 | After every `js2-XML` token, `js2-get-next-xml-token` makes it into a string (not 183 | fontified as such), and uses `js2-parse-expr` (and the normal tokenizer) to parse the 184 | expression in curly braces. It then wraps the expression in a `js2-xml-js-expr-node`. 185 | 186 | 187 | **Hypothesis**: Extend JS2-JSX mode and advice `js2-parse-xml-initializer` to our own XML 188 | parser when in said mode. Re-use as much of the js2 parser (e.g., js2-parse-member-expr) 189 | and use the grammar at https://facebook.github.io/jsx/ to coordinate between them 190 | -------------------------------------------------------------------------------- /js2-tests.el: -------------------------------------------------------------------------------- 1 | ;;; js2-tests.el --- Testing helpers from js2-mode 2 | 3 | ;; Copyright (C) 2009, 2011-2016 Free Software Foundation, Inc. 4 | 5 | ;;;; Commentary: 6 | ;; These are utilities taken from js2-mode/tests/parser.el 7 | ;; This file is licensed under the GPL v3 (full text at the end) 8 | 9 | ;;;; Code: 10 | 11 | (require 'ert) 12 | (require 'ert-x) 13 | (require 'js2-mode) 14 | (require 'cl-lib) 15 | 16 | (defvar rjsx--debug-indent 0) 17 | 18 | (defun rjsx--debug-tree-visitor (node endp) 19 | "Inserts a sexp with the JS2 ast into the current buffer. 20 | Debugging routine. NODE and ENDP are from `js2-visit-ast'." 21 | (if endp 22 | (progn (insert ")") (cl-decf rjsx--debug-indent)) 23 | (insert "\n" (make-string (* 2 rjsx--debug-indent) ?\ )) 24 | (insert "(" (js2-tt-name (js2-node-type node))) 25 | (cl-incf rjsx--debug-indent))) 26 | 27 | (defun rjsx--debug-tree (ast) 28 | "Return a string with a sexp of the node names in AST." 29 | (with-temp-buffer 30 | (js2-visit-ast ast #'rjsx--debug-tree-visitor) 31 | (buffer-substring-no-properties (point-min) (point-max)))) 32 | 33 | (defmacro js2-deftest (name buffer-contents &rest body) 34 | (declare (indent defun)) 35 | `(ert-deftest ,(intern (format "js2-%s" name)) () 36 | (with-temp-buffer 37 | (save-excursion 38 | (insert ,buffer-contents)) 39 | (unwind-protect 40 | (progn 41 | ,@body) 42 | (fundamental-mode))))) 43 | 44 | (defun js2-mode--and-parse () 45 | (js2-mode) 46 | (js2-reparse)) 47 | 48 | (defun js2-test-string-to-ast (s) 49 | (insert s) 50 | (js2-mode--and-parse) 51 | (should (null js2-mode-buffer-dirty-p)) 52 | js2-mode-ast) 53 | 54 | (cl-defun js2-test-parse-string (code-string &key syntax-error errors-count 55 | reference warnings-count) 56 | (ert-with-test-buffer (:name 'origin) 57 | (let ((ast (js2-test-string-to-ast code-string))) 58 | (if syntax-error 59 | (let ((errors (js2-ast-root-errors ast))) 60 | (should (= (or errors-count 1) (length errors))) 61 | (cl-destructuring-bind (_ pos len) (car (last errors)) 62 | (should (string= syntax-error (substring (concat code-string "") 63 | (1- pos) (+ pos len -1)))))) 64 | (should (= 0 (length (js2-ast-root-errors ast)))) 65 | (ert-with-test-buffer (:name 'copy) 66 | (js2-print-tree ast) 67 | (skip-chars-backward " \t\n") 68 | (should (string= (or reference code-string) 69 | (buffer-substring-no-properties 70 | (point-min) (point))))) 71 | (should (= (or warnings-count 0) 72 | (length (js2-ast-root-warnings ast)))))))) 73 | 74 | (cl-defmacro js2-deftest-parse (name code-string &key bind syntax-error errors-count 75 | reference warnings-count) 76 | "Parse CODE-STRING. If SYNTAX-ERROR is nil, print syntax tree 77 | with `js2-print-tree' and assert the result to be equal to 78 | REFERENCE, if present, or the original string. If SYNTAX-ERROR 79 | is passed, expect syntax error highlighting substring equal to 80 | SYNTAX-ERROR value. BIND defines bindings to apply them around 81 | the test." 82 | (declare (indent defun)) 83 | `(ert-deftest ,(intern (format "js2-%s" name)) () 84 | (let ,(append bind '((js2-basic-offset 2))) 85 | (js2-test-parse-string ,code-string 86 | :syntax-error ,syntax-error 87 | :errors-count ,errors-count 88 | :warnings-count ,warnings-count 89 | :reference ,reference)))) 90 | 91 | 92 | ;; I added the following version of the prior macro 93 | (cl-defmacro js2-deftest-parse-expected-failure (name code-string 94 | &key bind syntax-error errors-count 95 | reference warnings-count) 96 | "Parse CODE-STRING. If SYNTAX-ERROR is nil, print syntax tree 97 | with `js2-print-tree' and assert the result to be equal to 98 | REFERENCE, if present, or the original string. If SYNTAX-ERROR 99 | is passed, expect syntax error highlighting substring equal to 100 | SYNTAX-ERROR value. BIND defines bindings to apply them around 101 | the test." 102 | (declare (indent defun)) 103 | `(ert-deftest ,(intern (format "js2-%s" name)) () 104 | :expected-result :failed 105 | (let ,(append bind '((js2-basic-offset 2))) 106 | (js2-test-parse-string ,code-string 107 | :syntax-error ,syntax-error 108 | :errors-count ,errors-count 109 | :warnings-count ,warnings-count 110 | :reference ,reference)))) 111 | 112 | 113 | 114 | ;; GNU GENERAL PUBLIC LICENSE 115 | ;; Version 3, 29 June 2007 116 | 117 | ;; Copyright (C) 2007 Free Software Foundation, Inc. 118 | ;; Everyone is permitted to copy and distribute verbatim copies 119 | ;; of this license document, but changing it is not allowed. 120 | 121 | ;; Preamble 122 | 123 | ;; The GNU General Public License is a free, copyleft license for 124 | ;; software and other kinds of works. 125 | 126 | ;; The licenses for most software and other practical works are designed 127 | ;; to take away your freedom to share and change the works. By contrast, 128 | ;; the GNU General Public License is intended to guarantee your freedom to 129 | ;; share and change all versions of a program--to make sure it remains free 130 | ;; software for all its users. We, the Free Software Foundation, use the 131 | ;; GNU General Public License for most of our software; it applies also to 132 | ;; any other work released this way by its authors. You can apply it to 133 | ;; your programs, too. 134 | 135 | ;; When we speak of free software, we are referring to freedom, not 136 | ;; price. Our General Public Licenses are designed to make sure that you 137 | ;; have the freedom to distribute copies of free software (and charge for 138 | ;; them if you wish), that you receive source code or can get it if you 139 | ;; want it, that you can change the software or use pieces of it in new 140 | ;; free programs, and that you know you can do these things. 141 | 142 | ;; To protect your rights, we need to prevent others from denying you 143 | ;; these rights or asking you to surrender the rights. Therefore, you have 144 | ;; certain responsibilities if you distribute copies of the software, or if 145 | ;; you modify it: responsibilities to respect the freedom of others. 146 | 147 | ;; For example, if you distribute copies of such a program, whether 148 | ;; gratis or for a fee, you must pass on to the recipients the same 149 | ;; freedoms that you received. You must make sure that they, too, receive 150 | ;; or can get the source code. And you must show them these terms so they 151 | ;; know their rights. 152 | 153 | ;; Developers that use the GNU GPL protect your rights with two steps: 154 | ;; (1) assert copyright on the software, and (2) offer you this License 155 | ;; giving you legal permission to copy, distribute and/or modify it. 156 | 157 | ;; For the developers' and authors' protection, the GPL clearly explains 158 | ;; that there is no warranty for this free software. For both users' and 159 | ;; authors' sake, the GPL requires that modified versions be marked as 160 | ;; changed, so that their problems will not be attributed erroneously to 161 | ;; authors of previous versions. 162 | 163 | ;; Some devices are designed to deny users access to install or run 164 | ;; modified versions of the software inside them, although the manufacturer 165 | ;; can do so. This is fundamentally incompatible with the aim of 166 | ;; protecting users' freedom to change the software. The systematic 167 | ;; pattern of such abuse occurs in the area of products for individuals to 168 | ;; use, which is precisely where it is most unacceptable. Therefore, we 169 | ;; have designed this version of the GPL to prohibit the practice for those 170 | ;; products. If such problems arise substantially in other domains, we 171 | ;; stand ready to extend this provision to those domains in future versions 172 | ;; of the GPL, as needed to protect the freedom of users. 173 | 174 | ;; Finally, every program is threatened constantly by software patents. 175 | ;; States should not allow patents to restrict development and use of 176 | ;; software on general-purpose computers, but in those that do, we wish to 177 | ;; avoid the special danger that patents applied to a free program could 178 | ;; make it effectively proprietary. To prevent this, the GPL assures that 179 | ;; patents cannot be used to render the program non-free. 180 | 181 | ;; The precise terms and conditions for copying, distribution and 182 | ;; modification follow. 183 | 184 | ;; TERMS AND CONDITIONS 185 | 186 | ;; 0. Definitions. 187 | 188 | ;; "This License" refers to version 3 of the GNU General Public License. 189 | 190 | ;; "Copyright" also means copyright-like laws that apply to other kinds of 191 | ;; works, such as semiconductor masks. 192 | 193 | ;; "The Program" refers to any copyrightable work licensed under this 194 | ;; License. Each licensee is addressed as "you". "Licensees" and 195 | ;; "recipients" may be individuals or organizations. 196 | 197 | ;; To "modify" a work means to copy from or adapt all or part of the work 198 | ;; in a fashion requiring copyright permission, other than the making of an 199 | ;; exact copy. The resulting work is called a "modified version" of the 200 | ;; earlier work or a work "based on" the earlier work. 201 | 202 | ;; A "covered work" means either the unmodified Program or a work based 203 | ;; on the Program. 204 | 205 | ;; To "propagate" a work means to do anything with it that, without 206 | ;; permission, would make you directly or secondarily liable for 207 | ;; infringement under applicable copyright law, except executing it on a 208 | ;; computer or modifying a private copy. Propagation includes copying, 209 | ;; distribution (with or without modification), making available to the 210 | ;; public, and in some countries other activities as well. 211 | 212 | ;; To "convey" a work means any kind of propagation that enables other 213 | ;; parties to make or receive copies. Mere interaction with a user through 214 | ;; a computer network, with no transfer of a copy, is not conveying. 215 | 216 | ;; An interactive user interface displays "Appropriate Legal Notices" 217 | ;; to the extent that it includes a convenient and prominently visible 218 | ;; feature that (1) displays an appropriate copyright notice, and (2) 219 | ;; tells the user that there is no warranty for the work (except to the 220 | ;; extent that warranties are provided), that licensees may convey the 221 | ;; work under this License, and how to view a copy of this License. If 222 | ;; the interface presents a list of user commands or options, such as a 223 | ;; menu, a prominent item in the list meets this criterion. 224 | 225 | ;; 1. Source Code. 226 | 227 | ;; The "source code" for a work means the preferred form of the work 228 | ;; for making modifications to it. "Object code" means any non-source 229 | ;; form of a work. 230 | 231 | ;; A "Standard Interface" means an interface that either is an official 232 | ;; standard defined by a recognized standards body, or, in the case of 233 | ;; interfaces specified for a particular programming language, one that 234 | ;; is widely used among developers working in that language. 235 | 236 | ;; The "System Libraries" of an executable work include anything, other 237 | ;; than the work as a whole, that (a) is included in the normal form of 238 | ;; packaging a Major Component, but which is not part of that Major 239 | ;; Component, and (b) serves only to enable use of the work with that 240 | ;; Major Component, or to implement a Standard Interface for which an 241 | ;; implementation is available to the public in source code form. A 242 | ;; "Major Component", in this context, means a major essential component 243 | ;; (kernel, window system, and so on) of the specific operating system 244 | ;; (if any) on which the executable work runs, or a compiler used to 245 | ;; produce the work, or an object code interpreter used to run it. 246 | 247 | ;; The "Corresponding Source" for a work in object code form means all 248 | ;; the source code needed to generate, install, and (for an executable 249 | ;; work) run the object code and to modify the work, including scripts to 250 | ;; control those activities. However, it does not include the work's 251 | ;; System Libraries, or general-purpose tools or generally available free 252 | ;; programs which are used unmodified in performing those activities but 253 | ;; which are not part of the work. For example, Corresponding Source 254 | ;; includes interface definition files associated with source files for 255 | ;; the work, and the source code for shared libraries and dynamically 256 | ;; linked subprograms that the work is specifically designed to require, 257 | ;; such as by intimate data communication or control flow between those 258 | ;; subprograms and other parts of the work. 259 | 260 | ;; The Corresponding Source need not include anything that users 261 | ;; can regenerate automatically from other parts of the Corresponding 262 | ;; Source. 263 | 264 | ;; The Corresponding Source for a work in source code form is that 265 | ;; same work. 266 | 267 | ;; 2. Basic Permissions. 268 | 269 | ;; All rights granted under this License are granted for the term of 270 | ;; copyright on the Program, and are irrevocable provided the stated 271 | ;; conditions are met. This License explicitly affirms your unlimited 272 | ;; permission to run the unmodified Program. The output from running a 273 | ;; covered work is covered by this License only if the output, given its 274 | ;; content, constitutes a covered work. This License acknowledges your 275 | ;; rights of fair use or other equivalent, as provided by copyright law. 276 | 277 | ;; You may make, run and propagate covered works that you do not 278 | ;; convey, without conditions so long as your license otherwise remains 279 | ;; in force. You may convey covered works to others for the sole purpose 280 | ;; of having them make modifications exclusively for you, or provide you 281 | ;; with facilities for running those works, provided that you comply with 282 | ;; the terms of this License in conveying all material for which you do 283 | ;; not control copyright. Those thus making or running the covered works 284 | ;; for you must do so exclusively on your behalf, under your direction 285 | ;; and control, on terms that prohibit them from making any copies of 286 | ;; your copyrighted material outside their relationship with you. 287 | 288 | ;; Conveying under any other circumstances is permitted solely under 289 | ;; the conditions stated below. Sublicensing is not allowed; section 10 290 | ;; makes it unnecessary. 291 | 292 | ;; 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 293 | 294 | ;; No covered work shall be deemed part of an effective technological 295 | ;; measure under any applicable law fulfilling obligations under article 296 | ;; 11 of the WIPO copyright treaty adopted on 20 December 1996, or 297 | ;; similar laws prohibiting or restricting circumvention of such 298 | ;; measures. 299 | 300 | ;; When you convey a covered work, you waive any legal power to forbid 301 | ;; circumvention of technological measures to the extent such circumvention 302 | ;; is effected by exercising rights under this License with respect to 303 | ;; the covered work, and you disclaim any intention to limit operation or 304 | ;; modification of the work as a means of enforcing, against the work's 305 | ;; users, your or third parties' legal rights to forbid circumvention of 306 | ;; technological measures. 307 | 308 | ;; 4. Conveying Verbatim Copies. 309 | 310 | ;; You may convey verbatim copies of the Program's source code as you 311 | ;; receive it, in any medium, provided that you conspicuously and 312 | ;; appropriately publish on each copy an appropriate copyright notice; 313 | ;; keep intact all notices stating that this License and any 314 | ;; non-permissive terms added in accord with section 7 apply to the code; 315 | ;; keep intact all notices of the absence of any warranty; and give all 316 | ;; recipients a copy of this License along with the Program. 317 | 318 | ;; You may charge any price or no price for each copy that you convey, 319 | ;; and you may offer support or warranty protection for a fee. 320 | 321 | ;; 5. Conveying Modified Source Versions. 322 | 323 | ;; You may convey a work based on the Program, or the modifications to 324 | ;; produce it from the Program, in the form of source code under the 325 | ;; terms of section 4, provided that you also meet all of these conditions: 326 | 327 | ;; a) The work must carry prominent notices stating that you modified 328 | ;; it, and giving a relevant date. 329 | 330 | ;; b) The work must carry prominent notices stating that it is 331 | ;; released under this License and any conditions added under section 332 | ;; 7. This requirement modifies the requirement in section 4 to 333 | ;; "keep intact all notices". 334 | 335 | ;; c) You must license the entire work, as a whole, under this 336 | ;; License to anyone who comes into possession of a copy. This 337 | ;; License will therefore apply, along with any applicable section 7 338 | ;; additional terms, to the whole of the work, and all its parts, 339 | ;; regardless of how they are packaged. This License gives no 340 | ;; permission to license the work in any other way, but it does not 341 | ;; invalidate such permission if you have separately received it. 342 | 343 | ;; d) If the work has interactive user interfaces, each must display 344 | ;; Appropriate Legal Notices; however, if the Program has interactive 345 | ;; interfaces that do not display Appropriate Legal Notices, your 346 | ;; work need not make them do so. 347 | 348 | ;; A compilation of a covered work with other separate and independent 349 | ;; works, which are not by their nature extensions of the covered work, 350 | ;; and which are not combined with it such as to form a larger program, 351 | ;; in or on a volume of a storage or distribution medium, is called an 352 | ;; "aggregate" if the compilation and its resulting copyright are not 353 | ;; used to limit the access or legal rights of the compilation's users 354 | ;; beyond what the individual works permit. Inclusion of a covered work 355 | ;; in an aggregate does not cause this License to apply to the other 356 | ;; parts of the aggregate. 357 | 358 | ;; 6. Conveying Non-Source Forms. 359 | 360 | ;; You may convey a covered work in object code form under the terms 361 | ;; of sections 4 and 5, provided that you also convey the 362 | ;; machine-readable Corresponding Source under the terms of this License, 363 | ;; in one of these ways: 364 | 365 | ;; a) Convey the object code in, or embodied in, a physical product 366 | ;; (including a physical distribution medium), accompanied by the 367 | ;; Corresponding Source fixed on a durable physical medium 368 | ;; customarily used for software interchange. 369 | 370 | ;; b) Convey the object code in, or embodied in, a physical product 371 | ;; (including a physical distribution medium), accompanied by a 372 | ;; written offer, valid for at least three years and valid for as 373 | ;; long as you offer spare parts or customer support for that product 374 | ;; model, to give anyone who possesses the object code either (1) a 375 | ;; copy of the Corresponding Source for all the software in the 376 | ;; product that is covered by this License, on a durable physical 377 | ;; medium customarily used for software interchange, for a price no 378 | ;; more than your reasonable cost of physically performing this 379 | ;; conveying of source, or (2) access to copy the 380 | ;; Corresponding Source from a network server at no charge. 381 | 382 | ;; c) Convey individual copies of the object code with a copy of the 383 | ;; written offer to provide the Corresponding Source. This 384 | ;; alternative is allowed only occasionally and noncommercially, and 385 | ;; only if you received the object code with such an offer, in accord 386 | ;; with subsection 6b. 387 | 388 | ;; d) Convey the object code by offering access from a designated 389 | ;; place (gratis or for a charge), and offer equivalent access to the 390 | ;; Corresponding Source in the same way through the same place at no 391 | ;; further charge. You need not require recipients to copy the 392 | ;; Corresponding Source along with the object code. If the place to 393 | ;; copy the object code is a network server, the Corresponding Source 394 | ;; may be on a different server (operated by you or a third party) 395 | ;; that supports equivalent copying facilities, provided you maintain 396 | ;; clear directions next to the object code saying where to find the 397 | ;; Corresponding Source. Regardless of what server hosts the 398 | ;; Corresponding Source, you remain obligated to ensure that it is 399 | ;; available for as long as needed to satisfy these requirements. 400 | 401 | ;; e) Convey the object code using peer-to-peer transmission, provided 402 | ;; you inform other peers where the object code and Corresponding 403 | ;; Source of the work are being offered to the general public at no 404 | ;; charge under subsection 6d. 405 | 406 | ;; A separable portion of the object code, whose source code is excluded 407 | ;; from the Corresponding Source as a System Library, need not be 408 | ;; included in conveying the object code work. 409 | 410 | ;; A "User Product" is either (1) a "consumer product", which means any 411 | ;; tangible personal property which is normally used for personal, family, 412 | ;; or household purposes, or (2) anything designed or sold for incorporation 413 | ;; into a dwelling. In determining whether a product is a consumer product, 414 | ;; doubtful cases shall be resolved in favor of coverage. For a particular 415 | ;; product received by a particular user, "normally used" refers to a 416 | ;; typical or common use of that class of product, regardless of the status 417 | ;; of the particular user or of the way in which the particular user 418 | ;; actually uses, or expects or is expected to use, the product. A product 419 | ;; is a consumer product regardless of whether the product has substantial 420 | ;; commercial, industrial or non-consumer uses, unless such uses represent 421 | ;; the only significant mode of use of the product. 422 | 423 | ;; "Installation Information" for a User Product means any methods, 424 | ;; procedures, authorization keys, or other information required to install 425 | ;; and execute modified versions of a covered work in that User Product from 426 | ;; a modified version of its Corresponding Source. The information must 427 | ;; suffice to ensure that the continued functioning of the modified object 428 | ;; code is in no case prevented or interfered with solely because 429 | ;; modification has been made. 430 | 431 | ;; If you convey an object code work under this section in, or with, or 432 | ;; specifically for use in, a User Product, and the conveying occurs as 433 | ;; part of a transaction in which the right of possession and use of the 434 | ;; User Product is transferred to the recipient in perpetuity or for a 435 | ;; fixed term (regardless of how the transaction is characterized), the 436 | ;; Corresponding Source conveyed under this section must be accompanied 437 | ;; by the Installation Information. But this requirement does not apply 438 | ;; if neither you nor any third party retains the ability to install 439 | ;; modified object code on the User Product (for example, the work has 440 | ;; been installed in ROM). 441 | 442 | ;; The requirement to provide Installation Information does not include a 443 | ;; requirement to continue to provide support service, warranty, or updates 444 | ;; for a work that has been modified or installed by the recipient, or for 445 | ;; the User Product in which it has been modified or installed. Access to a 446 | ;; network may be denied when the modification itself materially and 447 | ;; adversely affects the operation of the network or violates the rules and 448 | ;; protocols for communication across the network. 449 | 450 | ;; Corresponding Source conveyed, and Installation Information provided, 451 | ;; in accord with this section must be in a format that is publicly 452 | ;; documented (and with an implementation available to the public in 453 | ;; source code form), and must require no special password or key for 454 | ;; unpacking, reading or copying. 455 | 456 | ;; 7. Additional Terms. 457 | 458 | ;; "Additional permissions" are terms that supplement the terms of this 459 | ;; License by making exceptions from one or more of its conditions. 460 | ;; Additional permissions that are applicable to the entire Program shall 461 | ;; be treated as though they were included in this License, to the extent 462 | ;; that they are valid under applicable law. If additional permissions 463 | ;; apply only to part of the Program, that part may be used separately 464 | ;; under those permissions, but the entire Program remains governed by 465 | ;; this License without regard to the additional permissions. 466 | 467 | ;; When you convey a copy of a covered work, you may at your option 468 | ;; remove any additional permissions from that copy, or from any part of 469 | ;; it. (Additional permissions may be written to require their own 470 | ;; removal in certain cases when you modify the work.) You may place 471 | ;; additional permissions on material, added by you to a covered work, 472 | ;; for which you have or can give appropriate copyright permission. 473 | 474 | ;; Notwithstanding any other provision of this License, for material you 475 | ;; add to a covered work, you may (if authorized by the copyright holders of 476 | ;; that material) supplement the terms of this License with terms: 477 | 478 | ;; a) Disclaiming warranty or limiting liability differently from the 479 | ;; terms of sections 15 and 16 of this License; or 480 | 481 | ;; b) Requiring preservation of specified reasonable legal notices or 482 | ;; author attributions in that material or in the Appropriate Legal 483 | ;; Notices displayed by works containing it; or 484 | 485 | ;; c) Prohibiting misrepresentation of the origin of that material, or 486 | ;; requiring that modified versions of such material be marked in 487 | ;; reasonable ways as different from the original version; or 488 | 489 | ;; d) Limiting the use for publicity purposes of names of licensors or 490 | ;; authors of the material; or 491 | 492 | ;; e) Declining to grant rights under trademark law for use of some 493 | ;; trade names, trademarks, or service marks; or 494 | 495 | ;; f) Requiring indemnification of licensors and authors of that 496 | ;; material by anyone who conveys the material (or modified versions of 497 | ;; it) with contractual assumptions of liability to the recipient, for 498 | ;; any liability that these contractual assumptions directly impose on 499 | ;; those licensors and authors. 500 | 501 | ;; All other non-permissive additional terms are considered "further 502 | ;; restrictions" within the meaning of section 10. If the Program as you 503 | ;; received it, or any part of it, contains a notice stating that it is 504 | ;; governed by this License along with a term that is a further 505 | ;; restriction, you may remove that term. If a license document contains 506 | ;; a further restriction but permits relicensing or conveying under this 507 | ;; License, you may add to a covered work material governed by the terms 508 | ;; of that license document, provided that the further restriction does 509 | ;; not survive such relicensing or conveying. 510 | 511 | ;; If you add terms to a covered work in accord with this section, you 512 | ;; must place, in the relevant source files, a statement of the 513 | ;; additional terms that apply to those files, or a notice indicating 514 | ;; where to find the applicable terms. 515 | 516 | ;; Additional terms, permissive or non-permissive, may be stated in the 517 | ;; form of a separately written license, or stated as exceptions; 518 | ;; the above requirements apply either way. 519 | 520 | ;; 8. Termination. 521 | 522 | ;; You may not propagate or modify a covered work except as expressly 523 | ;; provided under this License. Any attempt otherwise to propagate or 524 | ;; modify it is void, and will automatically terminate your rights under 525 | ;; this License (including any patent licenses granted under the third 526 | ;; paragraph of section 11). 527 | 528 | ;; However, if you cease all violation of this License, then your 529 | ;; license from a particular copyright holder is reinstated (a) 530 | ;; provisionally, unless and until the copyright holder explicitly and 531 | ;; finally terminates your license, and (b) permanently, if the copyright 532 | ;; holder fails to notify you of the violation by some reasonable means 533 | ;; prior to 60 days after the cessation. 534 | 535 | ;; Moreover, your license from a particular copyright holder is 536 | ;; reinstated permanently if the copyright holder notifies you of the 537 | ;; violation by some reasonable means, this is the first time you have 538 | ;; received notice of violation of this License (for any work) from that 539 | ;; copyright holder, and you cure the violation prior to 30 days after 540 | ;; your receipt of the notice. 541 | 542 | ;; Termination of your rights under this section does not terminate the 543 | ;; licenses of parties who have received copies or rights from you under 544 | ;; this License. If your rights have been terminated and not permanently 545 | ;; reinstated, you do not qualify to receive new licenses for the same 546 | ;; material under section 10. 547 | 548 | ;; 9. Acceptance Not Required for Having Copies. 549 | 550 | ;; You are not required to accept this License in order to receive or 551 | ;; run a copy of the Program. Ancillary propagation of a covered work 552 | ;; occurring solely as a consequence of using peer-to-peer transmission 553 | ;; to receive a copy likewise does not require acceptance. However, 554 | ;; nothing other than this License grants you permission to propagate or 555 | ;; modify any covered work. These actions infringe copyright if you do 556 | ;; not accept this License. Therefore, by modifying or propagating a 557 | ;; covered work, you indicate your acceptance of this License to do so. 558 | 559 | ;; 10. Automatic Licensing of Downstream Recipients. 560 | 561 | ;; Each time you convey a covered work, the recipient automatically 562 | ;; receives a license from the original licensors, to run, modify and 563 | ;; propagate that work, subject to this License. You are not responsible 564 | ;; for enforcing compliance by third parties with this License. 565 | 566 | ;; An "entity transaction" is a transaction transferring control of an 567 | ;; organization, or substantially all assets of one, or subdividing an 568 | ;; organization, or merging organizations. If propagation of a covered 569 | ;; work results from an entity transaction, each party to that 570 | ;; transaction who receives a copy of the work also receives whatever 571 | ;; licenses to the work the party's predecessor in interest had or could 572 | ;; give under the previous paragraph, plus a right to possession of the 573 | ;; Corresponding Source of the work from the predecessor in interest, if 574 | ;; the predecessor has it or can get it with reasonable efforts. 575 | 576 | ;; You may not impose any further restrictions on the exercise of the 577 | ;; rights granted or affirmed under this License. For example, you may 578 | ;; not impose a license fee, royalty, or other charge for exercise of 579 | ;; rights granted under this License, and you may not initiate litigation 580 | ;; (including a cross-claim or counterclaim in a lawsuit) alleging that 581 | ;; any patent claim is infringed by making, using, selling, offering for 582 | ;; sale, or importing the Program or any portion of it. 583 | 584 | ;; 11. Patents. 585 | 586 | ;; A "contributor" is a copyright holder who authorizes use under this 587 | ;; License of the Program or a work on which the Program is based. The 588 | ;; work thus licensed is called the contributor's "contributor version". 589 | 590 | ;; A contributor's "essential patent claims" are all patent claims 591 | ;; owned or controlled by the contributor, whether already acquired or 592 | ;; hereafter acquired, that would be infringed by some manner, permitted 593 | ;; by this License, of making, using, or selling its contributor version, 594 | ;; but do not include claims that would be infringed only as a 595 | ;; consequence of further modification of the contributor version. For 596 | ;; purposes of this definition, "control" includes the right to grant 597 | ;; patent sublicenses in a manner consistent with the requirements of 598 | ;; this License. 599 | 600 | ;; Each contributor grants you a non-exclusive, worldwide, royalty-free 601 | ;; patent license under the contributor's essential patent claims, to 602 | ;; make, use, sell, offer for sale, import and otherwise run, modify and 603 | ;; propagate the contents of its contributor version. 604 | 605 | ;; In the following three paragraphs, a "patent license" is any express 606 | ;; agreement or commitment, however denominated, not to enforce a patent 607 | ;; (such as an express permission to practice a patent or covenant not to 608 | ;; sue for patent infringement). To "grant" such a patent license to a 609 | ;; party means to make such an agreement or commitment not to enforce a 610 | ;; patent against the party. 611 | 612 | ;; If you convey a covered work, knowingly relying on a patent license, 613 | ;; and the Corresponding Source of the work is not available for anyone 614 | ;; to copy, free of charge and under the terms of this License, through a 615 | ;; publicly available network server or other readily accessible means, 616 | ;; then you must either (1) cause the Corresponding Source to be so 617 | ;; available, or (2) arrange to deprive yourself of the benefit of the 618 | ;; patent license for this particular work, or (3) arrange, in a manner 619 | ;; consistent with the requirements of this License, to extend the patent 620 | ;; license to downstream recipients. "Knowingly relying" means you have 621 | ;; actual knowledge that, but for the patent license, your conveying the 622 | ;; covered work in a country, or your recipient's use of the covered work 623 | ;; in a country, would infringe one or more identifiable patents in that 624 | ;; country that you have reason to believe are valid. 625 | 626 | ;; If, pursuant to or in connection with a single transaction or 627 | ;; arrangement, you convey, or propagate by procuring conveyance of, a 628 | ;; covered work, and grant a patent license to some of the parties 629 | ;; receiving the covered work authorizing them to use, propagate, modify 630 | ;; or convey a specific copy of the covered work, then the patent license 631 | ;; you grant is automatically extended to all recipients of the covered 632 | ;; work and works based on it. 633 | 634 | ;; A patent license is "discriminatory" if it does not include within 635 | ;; the scope of its coverage, prohibits the exercise of, or is 636 | ;; conditioned on the non-exercise of one or more of the rights that are 637 | ;; specifically granted under this License. You may not convey a covered 638 | ;; work if you are a party to an arrangement with a third party that is 639 | ;; in the business of distributing software, under which you make payment 640 | ;; to the third party based on the extent of your activity of conveying 641 | ;; the work, and under which the third party grants, to any of the 642 | ;; parties who would receive the covered work from you, a discriminatory 643 | ;; patent license (a) in connection with copies of the covered work 644 | ;; conveyed by you (or copies made from those copies), or (b) primarily 645 | ;; for and in connection with specific products or compilations that 646 | ;; contain the covered work, unless you entered into that arrangement, 647 | ;; or that patent license was granted, prior to 28 March 2007. 648 | 649 | ;; Nothing in this License shall be construed as excluding or limiting 650 | ;; any implied license or other defenses to infringement that may 651 | ;; otherwise be available to you under applicable patent law. 652 | 653 | ;; 12. No Surrender of Others' Freedom. 654 | 655 | ;; If conditions are imposed on you (whether by court order, agreement or 656 | ;; otherwise) that contradict the conditions of this License, they do not 657 | ;; excuse you from the conditions of this License. If you cannot convey a 658 | ;; covered work so as to satisfy simultaneously your obligations under this 659 | ;; License and any other pertinent obligations, then as a consequence you may 660 | ;; not convey it at all. For example, if you agree to terms that obligate you 661 | ;; to collect a royalty for further conveying from those to whom you convey 662 | ;; the Program, the only way you could satisfy both those terms and this 663 | ;; License would be to refrain entirely from conveying the Program. 664 | 665 | ;; 13. Use with the GNU Affero General Public License. 666 | 667 | ;; Notwithstanding any other provision of this License, you have 668 | ;; permission to link or combine any covered work with a work licensed 669 | ;; under version 3 of the GNU Affero General Public License into a single 670 | ;; combined work, and to convey the resulting work. The terms of this 671 | ;; License will continue to apply to the part which is the covered work, 672 | ;; but the special requirements of the GNU Affero General Public License, 673 | ;; section 13, concerning interaction through a network will apply to the 674 | ;; combination as such. 675 | 676 | ;; 14. Revised Versions of this License. 677 | 678 | ;; The Free Software Foundation may publish revised and/or new versions of 679 | ;; the GNU General Public License from time to time. Such new versions will 680 | ;; be similar in spirit to the present version, but may differ in detail to 681 | ;; address new problems or concerns. 682 | 683 | ;; Each version is given a distinguishing version number. If the 684 | ;; Program specifies that a certain numbered version of the GNU General 685 | ;; Public License "or any later version" applies to it, you have the 686 | ;; option of following the terms and conditions either of that numbered 687 | ;; version or of any later version published by the Free Software 688 | ;; Foundation. If the Program does not specify a version number of the 689 | ;; GNU General Public License, you may choose any version ever published 690 | ;; by the Free Software Foundation. 691 | 692 | ;; If the Program specifies that a proxy can decide which future 693 | ;; versions of the GNU General Public License can be used, that proxy's 694 | ;; public statement of acceptance of a version permanently authorizes you 695 | ;; to choose that version for the Program. 696 | 697 | ;; Later license versions may give you additional or different 698 | ;; permissions. However, no additional obligations are imposed on any 699 | ;; author or copyright holder as a result of your choosing to follow a 700 | ;; later version. 701 | 702 | ;; 15. Disclaimer of Warranty. 703 | 704 | ;; THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 705 | ;; APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 706 | ;; HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 707 | ;; OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 708 | ;; THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 709 | ;; PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 710 | ;; IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 711 | ;; ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 712 | 713 | ;; 16. Limitation of Liability. 714 | 715 | ;; IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 716 | ;; WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 717 | ;; THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 718 | ;; GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 719 | ;; USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 720 | ;; DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 721 | ;; PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 722 | ;; EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 723 | ;; SUCH DAMAGES. 724 | 725 | ;; 17. Interpretation of Sections 15 and 16. 726 | 727 | ;; If the disclaimer of warranty and limitation of liability provided 728 | ;; above cannot be given local legal effect according to their terms, 729 | ;; reviewing courts shall apply local law that most closely approximates 730 | ;; an absolute waiver of all civil liability in connection with the 731 | ;; Program, unless a warranty or assumption of liability accompanies a 732 | ;; copy of the Program in return for a fee. 733 | 734 | ;; END OF TERMS AND CONDITIONS 735 | 736 | ;; How to Apply These Terms to Your New Programs 737 | 738 | ;; If you develop a new program, and you want it to be of the greatest 739 | ;; possible use to the public, the best way to achieve this is to make it 740 | ;; free software which everyone can redistribute and change under these terms. 741 | 742 | ;; To do so, attach the following notices to the program. It is safest 743 | ;; to attach them to the start of each source file to most effectively 744 | ;; state the exclusion of warranty; and each file should have at least 745 | ;; the "copyright" line and a pointer to where the full notice is found. 746 | 747 | ;; 748 | ;; Copyright (C) 749 | 750 | ;; This program is free software: you can redistribute it and/or modify 751 | ;; it under the terms of the GNU General Public License as published by 752 | ;; the Free Software Foundation, either version 3 of the License, or 753 | ;; (at your option) any later version. 754 | 755 | ;; This program is distributed in the hope that it will be useful, 756 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 757 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 758 | ;; GNU General Public License for more details. 759 | 760 | ;; You should have received a copy of the GNU General Public License 761 | ;; along with this program. If not, see . 762 | 763 | ;; Also add information on how to contact you by electronic and paper mail. 764 | 765 | ;; If the program does terminal interaction, make it output a short 766 | ;; notice like this when it starts in an interactive mode: 767 | 768 | ;; Copyright (C) 769 | ;; This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 770 | ;; This is free software, and you are welcome to redistribute it 771 | ;; under certain conditions; type `show c' for details. 772 | 773 | ;; The hypothetical commands `show w' and `show c' should show the appropriate 774 | ;; parts of the General Public License. Of course, your program's commands 775 | ;; might be different; for a GUI interface, you would use an "about box". 776 | 777 | ;; You should also get your employer (if you work as a programmer) or school, 778 | ;; if any, to sign a "copyright disclaimer" for the program, if necessary. 779 | ;; For more information on this, and how to apply and follow the GNU GPL, see 780 | ;; . 781 | 782 | ;; The GNU General Public License does not permit incorporating your program 783 | ;; into proprietary programs. If your program is a subroutine library, you 784 | ;; may consider it more useful to permit linking proprietary applications with 785 | ;; the library. If this is what you want to do, use the GNU Lesser General 786 | ;; Public License instead of this License. But first, please read 787 | ;; . 788 | 789 | 790 | (provide 'js2-tests) 791 | ;;; js2-tests.el ends here 792 | -------------------------------------------------------------------------------- /key-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felipeochoa/rjsx-mode/b697fe4d92cc84fa99a7bcb476f815935ea0d919/key-demo.gif -------------------------------------------------------------------------------- /rjsx-mode.el: -------------------------------------------------------------------------------- 1 | ;;; rjsx-mode.el --- Real support for JSX -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2016 Felipe Ochoa 4 | 5 | ;; Author: Felipe Ochoa 6 | ;; URL: https://github.com/felipeochoa/rjsx-mode/ 7 | ;; Package-Requires: ((emacs "24.4") (js2-mode "20170504")) 8 | ;; Version: 1.1 9 | ;; Keywords: languages 10 | 11 | ;;; Commentary: 12 | ;; Defines a major mode `rjsx-mode' based on `js2-mode' for editing 13 | ;; JSX files. `rjsx-mode' extends the parser in `js2-mode' to support 14 | ;; the full JSX syntax. This means you get all of the `js2' features 15 | ;; plus proper syntax checking and highlighting of JSX code blocks. 16 | ;; 17 | ;; Some features that this mode adds to js2: 18 | ;; 19 | ;; - Highlighting JSX tag names and attributes (using the rjsx-tag and 20 | ;; rjsx-attr faces) 21 | ;; - Highlight undeclared JSX components 22 | ;; - Parsing the spread operator {...otherProps} 23 | ;; - Parsing && and || in child expressions {cond && } 24 | ;; - Parsing ternary expressions {toggle ? : } 25 | ;; 26 | ;; Additionally, since rjsx-mode extends the js2 AST, utilities using 27 | ;; the parse tree gain access to the JSX structure. 28 | 29 | ;;; Code: 30 | 31 | ;;;; Basic mode definitions 32 | 33 | (require 'cl-lib) 34 | (require 'js2-mode) 35 | (eval-when-compile (require 'subr-x)) 36 | (require 'newcomment) 37 | (require 'sgml-mode) 38 | 39 | (defgroup rjsx-mode nil 40 | "Support for JSX." 41 | :group 'js2-mode) 42 | 43 | (defcustom rjsx-max-size-for-frequent-reparse 100000 44 | "Buffers with fewer than this many characters will be parsed more frequently. 45 | Set this to 0 to disable the reparsing altogether. The frequent 46 | parsing supports the magic `rjsx-electric-lt' and 47 | `rjsx-delete-creates-full-tag' behaviors." 48 | :group 'rjsx-mode 49 | :type 'integer) 50 | 51 | ;;;###autoload 52 | (define-derived-mode rjsx-mode js2-mode "RJSX" 53 | "Major mode for editing JSX files." 54 | :lighter ":RJSX" 55 | :group 'rjsx-mode 56 | (setq-local comment-use-syntax nil) 57 | (setq-local comment-start-skip "[[:space:]]*\\(//+\\|{?/\\*+\\)") 58 | ;; \n is included to get arround `comment-normalize-vars' and `comment-only-p' 59 | (setq-local comment-end-skip "\\(\\*+/}?[[:space:]]*\\)\n?\\|\n") 60 | (setq-local comment-region-function 'rjsx-comment-region-function) 61 | (setq-local uncomment-region-function 'rjsx-uncomment-region-function) 62 | (setq-local comment-quote-nested-function 'rjsx-comment-quote-nested-function) 63 | (setq-local indent-line-function 'rjsx-indent-line) 64 | (setq-local indent-region-function 'rjsx-indent-region)) 65 | 66 | ;;;###autoload 67 | (define-minor-mode rjsx-minor-mode 68 | "Minor mode for parsing JSX syntax into an AST." 69 | :lighter " rjsx" 70 | (if rjsx-minor-mode 71 | (js2-minor-mode 1) 72 | (js2-minor-mode 0))) 73 | 74 | ;;;###autoload 75 | (add-to-list 'auto-mode-alist '("\\.jsx\\'" . rjsx-mode)) 76 | 77 | (defun rjsx-parse-xml-initializer (orig-fun) 78 | "Dispatch the xml parser based on variable `rjsx-mode' being active or not. 79 | This function is used to advise `js2-parse-xml-initializer' (ORIG-FUN) using 80 | the `:around' combinator. JS2-PARSER is the original XML parser." 81 | (if (or (eq major-mode 'rjsx-mode) rjsx-minor-mode) 82 | (rjsx-parse-top-xml) 83 | (apply orig-fun nil))) 84 | 85 | (advice-add 'js2-parse-xml-initializer :around #'rjsx-parse-xml-initializer) 86 | 87 | (defun rjsx-unadvice-js2 () 88 | "Remove the rjsx advice on the js2 parser. This will cause rjsx to stop working globally." 89 | (advice-remove 'js2-parse-xml-initializer #'rjsx-parse-xml-initializer) 90 | (advice-remove 'js--looking-at-operator-p #'rjsx--js--looking-at-operator-p-advice)) 91 | 92 | 93 | (defface rjsx-tag 94 | '((t . (:inherit font-lock-function-name-face))) 95 | "`rjsx-mode' face used to highlight JSX tag names." 96 | :group 'rjsx-mode) 97 | 98 | (defface rjsx-attr 99 | '((t . (:inherit font-lock-variable-name-face))) 100 | "`rjsx-mode' face used to highlight JSX attribute names." 101 | :group 'rjsx-mode) 102 | 103 | (defface rjsx-text 104 | '((t . (:inherit font-lock-string-face))) 105 | "`rjsx-mode' face used to highlight JSX text." 106 | :group 'rjsx-mode) 107 | 108 | (defface rjsx-tag-bracket-face 109 | '((t . (:inherit default))) 110 | "`rjsx-mode' face used to highlight `<', `/', and `>'." 111 | :group 'rjsx-mode) 112 | 113 | 114 | ;;;; Parser constants struct definitions 115 | 116 | ;; Token types for XML nodes. We need to re-use some unused values to 117 | ;; not mess up the vectors that js2 has set up 118 | (defvar rjsx-JSX js2-ENUM_INIT_KEYS) 119 | (defvar rjsx-JSX-CLOSE js2-ENUM_INIT_VALUES) 120 | (defvar rjsx-JSX-IDENT js2-ENUM_INIT_ARRAY) 121 | (defvar rjsx-JSX-MEMBER js2-ENUM_NEXT) 122 | (defvar rjsx-JSX-ATTR js2-ENUM_ID) 123 | (defvar rjsx-JSX-SPREAD js2-REF_NS_MEMBER) 124 | (defvar rjsx-JSX-TEXT js2-ESCXMLTEXT) 125 | (defvar rjsx-JSX-EXPRESSION js2-ESCXMLATTR) 126 | 127 | (dolist (sym '(rjsx-JSX rjsx-JSX-CLOSE rjsx-JSX-IDENT rjsx-JSX-MEMBER rjsx-JSX-ATTR 128 | rjsx-JSX-SPREAD rjsx-JSX-TEXT rjsx-JSX-EXPRESSION)) 129 | (aset js2-token-names (symbol-value sym) (downcase (substring (symbol-name sym) 5))) 130 | (puthash sym (symbol-value sym) js2-token-codes)) 131 | 132 | (js2-msg "msg.bad.jsx.ident" "invalid JSX identifier") 133 | (js2-msg "msg.invalid.jsx.string" "invalid JSX string (cannot contain delimiter in string body)") 134 | (js2-msg "msg.mismatched.close.tag" "mismatched closing JSX tag; expected `%s'") 135 | (js2-msg "msg.no.gt.in.opener" "missing `>' in opening tag") 136 | (js2-msg "msg.no.gt.in.closer" "missing `>' in closing tag") 137 | (js2-msg "msg.no.gt.after.slash" "missing `>' after `/' in self-closing tag") 138 | (js2-msg "msg.no.rc.after.spread" "missing `}' after spread-prop") 139 | (js2-msg "msg.no.value.after.jsx.prop" "missing value after prop `%s'") 140 | (js2-msg "msg.no.dots.in.prop.spread" "missing `...' in spread prop") 141 | (js2-msg "msg.no.rc.after.expr" "missing `}' after expression") 142 | (js2-msg "msg.empty.expr" "empty `{}' expression") 143 | 144 | 145 | (cl-defstruct (rjsx-node 146 | (:include js2-node (type rjsx-JSX)) 147 | (:constructor nil) 148 | (:constructor make-rjsx-node 149 | (&key (pos (js2-current-token-beg)) 150 | len 151 | name 152 | rjsx-props 153 | kids))) 154 | name ; AST node containing the parsed xml name or nil for fragments 155 | rjsx-props ; linked list of AST nodes (both attributes and spreads) 156 | kids ; linked list of child xml nodes 157 | closing-tag) ; AST node with the tag closer 158 | 159 | 160 | (js2--struct-put 'rjsx-node 'js2-visitor 'rjsx-node-visit) 161 | (js2--struct-put 'rjsx-node 'js2-printer 'rjsx-node-print) 162 | (defun rjsx-node-visit (ast callback) 163 | "Visit the `rjsx-node' children of AST, invoking CALLBACK on them." 164 | (let ((name (rjsx-node-name ast))) 165 | (when name (js2-visit-ast name callback))) 166 | (dolist (prop (rjsx-node-rjsx-props ast)) 167 | (js2-visit-ast prop callback)) 168 | (dolist (prop (rjsx-node-kids ast)) 169 | (js2-visit-ast prop callback)) 170 | (when (rjsx-node-closing-tag ast) 171 | (js2-visit-ast (rjsx-node-closing-tag ast) callback))) 172 | 173 | (defun rjsx-node-print (node indent-level) 174 | "Print the `rjsx-node' NODE at indent level INDENT-LEVEL." 175 | (insert (js2-make-pad indent-level) "<") 176 | (let ((name-n (rjsx-node-name node))) 177 | (when name-n (js2-print-ast name-n 0))) 178 | (dolist (attr (rjsx-node-rjsx-props node)) 179 | (insert " ") 180 | (js2-print-ast attr 0)) 181 | (let ((closer (rjsx-node-closing-tag node))) 182 | (if (null closer) 183 | (insert "/>") 184 | (insert ">") 185 | (dolist (child (rjsx-node-kids node)) 186 | (js2-print-ast child 0)) 187 | (js2-print-ast closer indent-level)))) 188 | 189 | (defun rjsx-node-opening-tag-name (node) 190 | "Return a string with NODE's opening tag including any namespace and member operations." 191 | (let ((name-n (rjsx-node-name node))) 192 | (cond 193 | ((null name-n) "") ; fragment 194 | ((rjsx-member-p name-n) (rjsx-member-full-name name-n)) 195 | ((rjsx-identifier-p name-n) (rjsx-identifier-full-name name-n)) 196 | (t "")))) ; js2-error-node 197 | 198 | (defun rjsx-node-push-prop (n rjsx-prop) 199 | "Extend rjsx-node N's rjsx-props with js2-node RJSX-PROP. 200 | Sets JSX-PROPS's parent to N." 201 | (let ((rjsx-props (rjsx-node-rjsx-props n))) 202 | (if rjsx-props 203 | (setcdr rjsx-props (nconc (cdr rjsx-props) (list rjsx-prop))) 204 | (setf (rjsx-node-rjsx-props n) (list rjsx-prop)))) 205 | (js2-node-add-children n rjsx-prop)) 206 | 207 | (defun rjsx-node-push-child (n kid) 208 | "Extend rjsx-node N's children with js2-node KID. 209 | Sets KID's parent to N." 210 | (let ((kids (rjsx-node-kids n))) 211 | (if kids 212 | (setcdr kids (nconc (cdr kids) (list kid))) 213 | (setf (rjsx-node-kids n) (list kid)))) 214 | (js2-node-add-children n kid)) 215 | 216 | 217 | (cl-defstruct (rjsx-closing-tag 218 | (:include js2-node (type rjsx-JSX-CLOSE)) 219 | (:constructor nil) 220 | (:constructor make-rjsx-closing-tag (&key pos len name))) 221 | name) ; An rjsx-identifier or rjsx-member node or nil for fragments 222 | 223 | (js2--struct-put 'rjsx-closing-tag 'js2-visitor 'rjsx-closing-tag-visit) 224 | (js2--struct-put 'rjsx-closing-tag 'js2-printer 'rjsx-closing-tag-print) 225 | 226 | (defun rjsx-closing-tag-visit (ast callback) 227 | "Visit the `rjsx-closing-tag' children of AST, invoking CALLBACK on them." 228 | (js2-visit-ast (rjsx-closing-tag-name ast) callback)) 229 | 230 | (defun rjsx-closing-tag-print (node indent-level) 231 | "Print the `rjsx-closing-tag' NODE at INDENT-LEVEL." 232 | (insert (js2-make-pad indent-level) "")) 233 | 234 | (defun rjsx-closing-tag-full-name (n) 235 | "Return the string with N's fully-namespaced name, or just name if it's not namespaced." 236 | (let ((child (rjsx-closing-tag-name n))) 237 | (cond 238 | ((null child) "") ; fragment 239 | ((rjsx-member-p child) (rjsx-member-full-name child)) 240 | ((rjsx-identifier-p child) (rjsx-identifier-full-name child)) 241 | (t "")))) 242 | 243 | (cl-defstruct (rjsx-identifier 244 | (:include js2-node (type rjsx-JSX-IDENT)) 245 | (:constructor nil) 246 | (:constructor make-rjsx-identifier (&key (pos (js2-current-token-beg)) 247 | len namespace name))) 248 | (namespace nil) 249 | name) ; js2-name-node 250 | 251 | (js2--struct-put 'rjsx-identifier 'js2-visitor 'rjsx-identifier-visit) 252 | (js2--struct-put 'rjsx-identifier 'js2-printer 'rjsx-identifier-print) 253 | 254 | (defun rjsx-identifier-visit (n callback) 255 | "Visit N's children can call CALLBACK on them." 256 | (js2-visit-ast (rjsx-identifier-name n) callback)) 257 | 258 | (defun rjsx-identifier-print (node indent-level) 259 | "Print the `rjsx-identifier' NODE at INDENT-LEVEL." 260 | (insert (js2-make-pad indent-level) (rjsx-identifier-full-name node))) 261 | 262 | (defun rjsx-identifier-full-name (n) 263 | "Return the string with N's fully-namespaced name, or just name if it's not namespaced." 264 | (if (rjsx-identifier-namespace n) 265 | (format "%s:%s" (rjsx-identifier-namespace n) (js2-name-node-name (rjsx-identifier-name n))) 266 | (js2-name-node-name (rjsx-identifier-name n)))) 267 | 268 | (cl-defstruct (rjsx-member 269 | (:include js2-node (type rjsx-JSX-MEMBER)) 270 | (:constructor nil) 271 | (:constructor make-rjsx-member (&key pos len dots-pos idents))) 272 | dots-pos ; List of positions of each dot 273 | idents) ; List of rjsx-identifier nodes 274 | 275 | (js2--struct-put 'rjsx-member 'js2-visitor 'rjsx-member-visit) 276 | (js2--struct-put 'rjsx-member 'js2-printer 'rjsx-member-print) 277 | 278 | (defun rjsx-member-visit (n callback) 279 | "Visit N's children and call CALLBACK on them." 280 | (dolist (ident (rjsx-member-idents n)) 281 | (js2-visit-ast ident callback))) 282 | 283 | (defun rjsx-member-print (node indent-level) 284 | "Print the `rjsx-member' NODE at INDENT-LEVEL." 285 | (insert (js2-make-pad indent-level) (rjsx-member-full-name node))) 286 | 287 | (defun rjsx-member-full-name (n) 288 | "Return the string with N's combined names together." 289 | (mapconcat #'rjsx-identifier-full-name 290 | ;; Fix #89. There could be an error node here 291 | (cl-remove-if-not #'rjsx-identifier-p (rjsx-member-idents n)) 292 | ".")) 293 | 294 | (cl-defstruct (rjsx-attr 295 | (:include js2-node (type rjsx-JSX-ATTR)) 296 | (:constructor nil) 297 | (:constructor make-rjsx-attr (&key (pos (js2-current-token-beg)) 298 | len name value))) 299 | name ; a rjsx-identifier 300 | value) ; a js2-expression 301 | 302 | (js2--struct-put 'rjsx-attr 'js2-visitor 'rjsx-attr-visit) 303 | (js2--struct-put 'rjsx-attr 'js2-printer 'rjsx-attr-print) 304 | 305 | (defun rjsx-attr-visit (ast callback) 306 | "Visit the `rjsx-attr' children of AST, invoking CALLBACK on them." 307 | (js2-visit-ast (rjsx-attr-name ast) callback) 308 | (js2-visit-ast (rjsx-attr-value ast) callback)) 309 | 310 | (defun rjsx-attr-print (node indent-level) 311 | "Print the `rjsx-attr' NODE at INDENT-LEVEL." 312 | (js2-print-ast (rjsx-attr-name node) indent-level) 313 | (unless (js2-empty-expr-node-p (rjsx-attr-value node)) 314 | (insert "=") 315 | (js2-print-ast (rjsx-attr-value node) 0))) 316 | 317 | (cl-defstruct (rjsx-spread 318 | (:include js2-node (type rjsx-JSX-SPREAD)) 319 | (:constructor nil) 320 | (:constructor make-rjsx-spread (&key pos len expr))) 321 | expr) ; a js2-expression 322 | 323 | (js2--struct-put 'rjsx-spread 'js2-visitor 'rjsx-spread-visit) 324 | (js2--struct-put 'rjsx-spread 'js2-printer 'rjsx-spread-print) 325 | 326 | (defun rjsx-spread-visit (ast callback) 327 | "Visit the `rjsx-spread' children of AST, invoking CALLBACK on them." 328 | (js2-visit-ast (rjsx-spread-expr ast) callback)) 329 | 330 | (defun rjsx-spread-print (node indent-level) 331 | "Print the `rjsx-spread' NODE at INDENT-LEVEL." 332 | (insert (js2-make-pad indent-level) "{...") 333 | (js2-print-ast (rjsx-spread-expr node) 0) 334 | (insert "}")) 335 | 336 | (cl-defstruct (rjsx-wrapped-expr 337 | (:include js2-node (type rjsx-JSX-TEXT)) 338 | (:constructor nil) 339 | (:constructor make-rjsx-wrapped-expr (&key pos len child))) 340 | child) 341 | 342 | (js2--struct-put 'rjsx-wrapped-expr 'js2-visitor 'rjsx-wrapped-expr-visit) 343 | (js2--struct-put 'rjsx-wrapped-expr 'js2-printer 'rjsx-wrapped-expr-print) 344 | 345 | (defun rjsx-wrapped-expr-visit (ast callback) 346 | "Visit the `rjsx-wrapped-expr' child of AST, invoking CALLBACK on them." 347 | (js2-visit-ast (rjsx-wrapped-expr-child ast) callback)) 348 | 349 | (defun rjsx-wrapped-expr-print (node indent-level) 350 | "Print the `rjsx-wrapped-expr' NODE at INDENT-LEVEL." 351 | (insert (js2-make-pad indent-level) "{") 352 | (js2-print-ast (rjsx-wrapped-expr-child node) indent-level) 353 | (insert "}")) 354 | 355 | (cl-defstruct (rjsx-text 356 | (:include js2-node (type rjsx-JSX-TEXT)) 357 | (:constructor nil) 358 | (:constructor make-rjsx-text (&key (pos (js2-current-token-beg)) 359 | (len (js2-current-token-len)) 360 | value))) 361 | value) ; a string 362 | 363 | (js2--struct-put 'rjsx-text 'js2-visitor 'js2-visit-none) 364 | (js2--struct-put 'rjsx-text 'js2-printer 'rjsx-text-print) 365 | 366 | (defun rjsx-text-print (node _indent-level) 367 | "Print the `rjsx-text' NODE at INDENT-LEVEL." 368 | ;; Text nodes include whitespace 369 | (insert (rjsx-text-value node))) 370 | 371 | 372 | ;;;; Recursive descent parsing 373 | (defvar rjsx-print-debug-message nil "If t will print out debug messages.") 374 | ;(setq rjsx-print-debug-message t) 375 | (defmacro rjsx-maybe-message (&rest args) 376 | "If debug is enabled, call `message' with ARGS." 377 | `(when rjsx-print-debug-message 378 | (message ,@args))) 379 | 380 | 381 | (defvar rjsx-text-syntax-table 382 | (let ((table (make-syntax-table (standard-syntax-table)))) 383 | ;; `js-indent-line' assumes that \n is not whitespace 384 | ;; Since it's not a comment delimiter in JSX text, punctuation 385 | ;; is the only other (semi) logical choice 386 | (modify-syntax-entry ?\n "." table) 387 | table)) 388 | 389 | (js2-deflocal rjsx-in-xml nil "Variable used to track which xml parsing function is the outermost one.") 390 | 391 | (define-error 'rjsx-eof-while-parsing "RJSX: EOF while parsing") 392 | 393 | (defmacro rjsx-handling-eof (&rest body) 394 | "Execute BODY and return the result of the last form. 395 | If BODY signals `rjsx-eof-while-parsing', instead report a syntax 396 | error and return a `js2-error-node'." 397 | `(let ((beg (js2-current-token-beg))) 398 | (condition-case nil 399 | (progn ,@body) 400 | (rjsx-eof-while-parsing 401 | (let ((len (- js2-ts-cursor beg))) 402 | (rjsx-maybe-message (format "Handling eof from %d" beg)) 403 | (js2-report-error "msg.syntax" nil beg len) 404 | (make-js2-error-node :pos beg :len len)))))) 405 | 406 | (defsubst rjsx-record-tag-bracket-face () 407 | "Fontify the current token with `rjsx-tag-bracket-face'." 408 | (js2-set-face (js2-current-token-beg) (js2-current-token-end) 'rjsx-tag-bracket-face 'record)) 409 | 410 | (defsubst rjsx-record-token-class (cls) 411 | "Record an 'rjsx-class text property with value CLS for the current token." 412 | (js2-record-text-property (js2-current-token-beg) (js2-current-token-end) 413 | 'rjsx-class cls)) 414 | 415 | (defun rjsx-parse-top-xml () 416 | "Parse a top level XML fragment. 417 | This is the entry point when ‘js2-parse-unary-expr’ finds a '<' character" 418 | (if rjsx-in-xml 419 | (rjsx-parse-xml) 420 | (let ((rjsx-in-xml t)) 421 | (rjsx-handling-eof (rjsx-parse-xml))))) 422 | 423 | (defun rjsx-parse-xml () 424 | "Parse a complete xml node from start to end tag." 425 | (rjsx-record-token-class '<) 426 | (rjsx-record-tag-bracket-face) 427 | (let ((pn (make-rjsx-node)) self-closing name-n name-str child child-name-str is-fragment) 428 | (rjsx-maybe-message "Starting rjsx-parse-xml after <") 429 | (if (setq child (rjsx-parse-empty-tag)) 430 | child 431 | (if (eq (js2-peek-token) js2-GT) 432 | (setq is-fragment t) 433 | (setf (rjsx-node-name pn) (setq name-n (rjsx-parse-member-or-ns 'rjsx-tag))) 434 | (if (js2-error-node-p name-n) 435 | (progn (rjsx-maybe-message "could not parse tag name") 436 | (make-js2-error-node :pos (js2-node-pos pn) :len (1+ (js2-node-len name-n)))) 437 | (js2-node-add-children pn name-n) 438 | (if js2-highlight-external-variables 439 | (let ((name-node (rjsx-identifier-name 440 | (if (rjsx-member-p name-n) 441 | (car (rjsx-member-idents name-n)) 442 | name-n))) 443 | (case-fold-search nil)) 444 | (when (string-match-p "^[[:upper:]]" (js2-name-node-name name-node)) 445 | (js2-record-name-node name-node))))) 446 | (rjsx-maybe-message "cleared tag name: '%s'" name-str) 447 | ;; Now parse the attributes 448 | (rjsx-parse-attributes pn) 449 | (rjsx-maybe-message "cleared attributes")) 450 | (setq name-str (rjsx-node-opening-tag-name pn)) 451 | (progn 452 | ;; Now parse either a self closing tag or the end of the opening tag 453 | (rjsx-maybe-message "next type: `%s'" (js2-peek-token)) 454 | (if (setq self-closing (js2-match-token js2-DIV)) 455 | (progn 456 | (rjsx-record-token-class 'self-closing-slash) 457 | (rjsx-record-tag-bracket-face) 458 | ;; TODO: How do we un-mark old slashes? 459 | (when (js2-must-match js2-GT "msg.no.gt.after.slash" 460 | (js2-node-pos pn) (- (js2-current-token-end) (js2-node-pos pn))) 461 | (rjsx-record-token-class '>) 462 | (rjsx-record-tag-bracket-face))) 463 | (when (js2-must-match js2-GT "msg.no.gt.in.opener" (js2-node-pos pn) (js2-node-len pn)) 464 | (rjsx-record-token-class '>) 465 | (rjsx-record-tag-bracket-face))) 466 | (rjsx-maybe-message "cleared opener closer, self-closing: %s" self-closing) 467 | (if self-closing 468 | (setf (js2-node-len pn) (- (js2-current-token-end) (js2-node-pos pn))) 469 | (while (not (rjsx-closing-tag-p (setq child (rjsx-parse-child is-fragment)))) 470 | ;; rjsx-parse-child calls our scanner, which always moves 471 | ;; forward at least one character. If it hits EOF, it 472 | ;; signals to our caller, so we don't have to worry about infinite loops here 473 | (rjsx-maybe-message "parsed child") 474 | (rjsx-node-push-child pn child) 475 | (if (= 0 (js2-node-len child)) ; TODO: Does this ever happen? 476 | (js2-get-token))) 477 | (setq child-name-str (rjsx-closing-tag-full-name child)) 478 | (unless (string= name-str child-name-str) 479 | (js2-report-error "msg.mismatched.close.tag" name-str (js2-node-pos child) (js2-node-len child))) 480 | (rjsx-maybe-message "cleared children for `%s'" name-str) 481 | (js2-node-add-children pn child) 482 | (setf (rjsx-node-closing-tag pn) child)) 483 | (rjsx-maybe-message "Returning completed XML node") 484 | (setf (js2-node-len pn) (- (js2-current-token-end) (js2-node-pos pn))) 485 | pn)))) 486 | 487 | (defun rjsx-parse-empty-tag () 488 | "Check if we are in an empty tag of the form `' and consume it if so. 489 | Returns a `js2-error-node' if we are in one or nil if not." 490 | (let ((beg (js2-current-token-beg))) 491 | (when (js2-match-token js2-DIV) 492 | (if (js2-match-token js2-GT) 493 | (progn ; We're in a block, likely created by us in `rjsx-electric-lt' 494 | ;; We only highlight the < to reduce the visual impact 495 | (js2-report-error "msg.syntax" nil beg 1) 496 | (make-js2-error-node :pos beg :len (- (js2-current-token-end) beg))) 497 | ;; TODO: This is probably an unmatched closing tag. We should 498 | ;; consume it, mark it an error, and move on 499 | (js2-unget-token) 500 | nil)))) 501 | 502 | (defun rjsx-parse-attributes (parent) 503 | "Parse all attributes, including key=value and {...spread}, and add them to PARENT." 504 | ;; Getting this function to not hang in the loop proved tricky. The 505 | ;; key is that `rjsx-parse-spread' and `rjsx-parse-single-attr' both 506 | ;; return `js2-error-node's if they fail to consume any tokens, 507 | ;; which signals to us that we just need to discard one token and 508 | ;; keep going. 509 | (let (attr 510 | (loop-terminators (list js2-DIV js2-GT js2-EOF js2-ERROR))) 511 | (while (not (memql (js2-peek-token) loop-terminators)) 512 | (rjsx-maybe-message "Starting loop. Next token type: %s\nToken pos: %s" (js2-peek-token) (js2-current-token-beg)) 513 | (setq attr 514 | (if (js2-match-token js2-LC) 515 | (or (rjsx-check-for-empty-curlies t) 516 | (prog1 (rjsx-parse-spread) 517 | (rjsx-maybe-message "Parsed spread"))) 518 | (rjsx-maybe-message "Parsing single attr") 519 | (rjsx-parse-single-attr))) 520 | (when (js2-error-node-p attr) (js2-get-token)) 521 | ; TODO: We should make this conditional on 522 | ; `js2-recover-from-parse-errors' 523 | (rjsx-node-push-prop parent attr)))) 524 | 525 | 526 | (cl-defun rjsx-check-for-empty-curlies (&optional dont-consume-rc &key check-for-comments warning) 527 | "If the following token is '}' set empty curly errors. 528 | If DONT-CONSUME-RC is non-nil, the matched right curly token 529 | won't be consumed. Returns a `js2-error-node' if the curlies are 530 | empty or nil otherwise. If CHECK-FOR-COMMENTS (a &KEY argument) 531 | is non-nil, this will check for comments inside the curlies and 532 | returns a `js2-empty-expr-node' if any are found. If WARNING (a 533 | &key argument) is non-nil, reports the empty curlies as a warning 534 | and not an error and also returns a `js2-empty-expr-node'. 535 | Assumes the current token is a '{'." 536 | (let ((beg (js2-current-token-beg)) end len) 537 | (when (js2-match-token js2-RC) 538 | (setq end (js2-current-token-end)) 539 | (setq len (- end beg)) 540 | (when dont-consume-rc 541 | (js2-unget-token)) 542 | (if check-for-comments (rjsx-maybe-message "Checking for comments between %d and %d" beg end)) 543 | (unless (and check-for-comments 544 | (dolist (comment js2-scanned-comments) 545 | (rjsx-maybe-message "Comment at %d, length=%d" 546 | (js2-node-pos comment) 547 | (js2-node-len comment)) 548 | ;; TODO: IF comments are in reverse document order, we should be able to 549 | ;; bail out early and know we didn't find one 550 | (when (and (>= (js2-node-pos comment) beg) 551 | (<= (+ (js2-node-pos comment) (js2-node-len comment)) end)) 552 | (cl-return-from rjsx-check-for-empty-curlies 553 | (make-js2-empty-expr-node :pos beg :len (- end beg)))))) 554 | (if warning 555 | (progn (js2-report-warning "msg.empty.expr" nil beg len) 556 | (make-js2-empty-expr-node :pos beg :len (- end beg))) 557 | (js2-report-error "msg.empty.expr" nil beg len) 558 | (make-js2-error-node :pos beg :len len)))))) 559 | 560 | 561 | (defun rjsx-parse-spread () 562 | "Parse an {...props} attribute." 563 | (let ((pn (make-rjsx-spread :pos (js2-current-token-beg))) 564 | (beg (js2-current-token-beg)) 565 | missing-dots expr) 566 | (setq missing-dots (not (js2-match-token js2-TRIPLEDOT))) 567 | ;; parse-assign-expr will go crazy if we're looking at `} /', so we 568 | ;; check for an empty spread first 569 | (if (js2-match-token js2-RC) 570 | (setq expr (make-js2-error-node :len 1)) 571 | (setq expr (js2-parse-assign-expr)) 572 | (when (js2-error-node-p expr) 573 | (pop js2-parsed-errors))) ; We'll add our own error 574 | (unless (or (js2-match-token js2-RC) (js2-error-node-p expr)) 575 | (js2-report-error "msg.no.rc.after.spread" nil 576 | beg (- (js2-current-token-end) beg))) 577 | (setf (rjsx-spread-expr pn) expr) 578 | (setf (js2-node-len pn) (- (js2-current-token-end) (js2-node-pos pn))) 579 | (js2-node-add-children pn expr) 580 | (if (js2-error-node-p expr) 581 | (js2-report-error "msg.syntax" nil beg (- (js2-current-token-end) beg)) 582 | (when missing-dots 583 | (js2-report-error "msg.no.dots.in.prop.spread" nil beg (js2-node-len pn)))) 584 | (if (= 0 (js2-node-len pn)) ; TODO: Is this ever possible? 585 | (make-js2-error-node :pos beg :len 0) 586 | pn))) 587 | 588 | (defun rjsx-parse-single-attr () 589 | "Parse an 'a=b' JSX attribute and return the corresponding XML node." 590 | (let ((pn (make-rjsx-attr)) name value beg) 591 | (setq name (rjsx-parse-identifier 'rjsx-attr)) ; Won't consume token on error 592 | (if (js2-error-node-p name) 593 | name 594 | (setf (rjsx-attr-name pn) name) 595 | (setq beg (js2-node-pos name)) 596 | (setf (js2-node-pos pn) beg) 597 | (js2-node-add-children pn name) 598 | (rjsx-maybe-message "Got the name for the attr: `%s'" (rjsx-identifier-full-name name)) 599 | (if (js2-match-token js2-ASSIGN) ; Won't consume on error 600 | (progn 601 | (rjsx-maybe-message "Matched the equals sign") 602 | (if (js2-match-token js2-LC) 603 | (setq value (rjsx-parse-wrapped-expr nil t)) 604 | (if (js2-match-token js2-STRING) 605 | (setq value (rjsx-parse-string)) 606 | (js2-report-error "msg.no.value.after.jsx.prop" (rjsx-identifier-full-name name) 607 | beg (- (js2-current-token-end) beg)) 608 | (setq value (make-js2-error-node :pos beg :len (js2-current-token-len)))))) 609 | (setq value (make-js2-empty-expr-node :pos (js2-current-token-end) :len 0))) 610 | (rjsx-maybe-message "value type: `%s'" (js2-node-type value)) 611 | (setf (rjsx-attr-value pn) value) 612 | (setf (js2-node-len pn) (- (js2-node-end value) (js2-node-pos pn))) 613 | (js2-node-add-children pn value) 614 | (rjsx-maybe-message "Finished single attribute.") 615 | pn))) 616 | 617 | (defun rjsx-parse-wrapped-expr (allow-empty skip-to-rc) 618 | "Parse a curly-brace-wrapped JS expression. 619 | If ALLOW-EMPTY is non-nil, will warn for empty braces, otherwise 620 | will signal a syntax error. If it does not find a right curly 621 | and SKIP-TO-RC is non-nil, after the expression, consumes tokens 622 | until the end of the JSX node" 623 | (rjsx-maybe-message "parsing wrapped expression") 624 | (let (pn 625 | (beg (js2-current-token-beg)) 626 | (child (rjsx-check-for-empty-curlies nil 627 | :check-for-comments allow-empty 628 | :warning allow-empty))) 629 | (if child 630 | (if allow-empty 631 | (make-rjsx-wrapped-expr :pos beg :len (js2-node-len child) :child child) 632 | child) ;; Will be an error node in this case 633 | (setq child (js2-parse-assign-expr)) 634 | (rjsx-maybe-message "parsed expression, type: `%s'" (js2-node-type child)) 635 | (setq pn (make-rjsx-wrapped-expr :pos beg :child child)) 636 | (js2-node-add-children pn child) 637 | (when (js2-error-node-p child) 638 | (pop js2-parsed-errors)) ; We'll record our own message after checking for RC 639 | (if (js2-match-token js2-RC) 640 | (rjsx-maybe-message "matched } after expression") 641 | (rjsx-maybe-message "did not match } after expression") 642 | (when skip-to-rc 643 | (while (not (memql (js2-get-token) (list js2-RC js2-EOF js2-DIV js2-GT))) 644 | (rjsx-maybe-message "Skipped over `%s'" (js2-current-token-string))) 645 | (when (memq (js2-current-token-type) (list js2-DIV js2-GT)) 646 | (js2-unget-token))) 647 | (unless (js2-error-node-p child) 648 | (js2-report-error "msg.no.rc.after.expr" nil beg 649 | (- (js2-current-token-beg) beg)))) 650 | (when (js2-error-node-p child) 651 | (js2-report-error "msg.syntax" nil beg (- (js2-current-token-end) beg))) 652 | (setf (js2-node-len pn) (- (js2-current-token-end) beg)) 653 | pn))) 654 | 655 | (defun rjsx-parse-string () 656 | "Verify that current token is a valid JSX string. 657 | Returns a `js2-error-node' if TOKEN-STRING is not a valid JSX 658 | string, otherwise returns a `js2-string-node'. (Strings are 659 | invalid if they contain the delimiting quote character inside)" 660 | (rjsx-maybe-message "Parsing string") 661 | (let* ((token (js2-current-token)) 662 | (beg (js2-token-beg token)) 663 | (len (- (js2-token-end token) beg)) 664 | (token-string (js2-token-string token)) ;; JS2 does not include the quote-chars 665 | (quote-char (char-before (js2-token-end token)))) 666 | (if (cl-position quote-char token-string) 667 | (progn 668 | (js2-report-error "msg.invalid.jsx.string" nil beg len) 669 | (make-js2-error-node :pos beg :len len)) 670 | (make-js2-string-node :pos beg :len len :value token-string)))) 671 | 672 | (cl-defun rjsx-parse-identifier (&optional face &key (allow-ns t)) 673 | "Parse a possibly namespaced identifier and fontify with FACE if given. 674 | Returns a `js2-error-node' if unable to parse. If the &key 675 | argument ALLOW-NS is nil, does not allow namespaced names." 676 | (if (js2-must-match-name "msg.bad.jsx.ident") 677 | (let ((pn (make-rjsx-identifier)) 678 | (beg (js2-current-token-beg)) 679 | (name-parts (list (js2-current-token-string))) 680 | (allow-colon allow-ns) 681 | (continue t) 682 | (prev-token-end (js2-current-token-end)) 683 | (name-start (js2-current-token-beg)) 684 | matched-colon) 685 | (while (and continue 686 | (or (and (memq (js2-peek-token) (list js2-SUB js2-ASSIGN_SUB)) 687 | (prog2 ; Ensure no whitespace between previous name and this dash 688 | (js2-get-token) 689 | (eq prev-token-end (js2-current-token-beg)) 690 | (js2-unget-token))) 691 | (and allow-colon (= (js2-peek-token) js2-COLON)))) 692 | (if (setq matched-colon (js2-match-token js2-COLON)) 693 | (setf (rjsx-identifier-namespace pn) (apply #'concat (nreverse name-parts)) 694 | allow-colon nil 695 | name-parts (list) 696 | name-start nil) 697 | (when (= (js2-get-token) js2-ASSIGN_SUB) ; Otherwise it's a js2-SUB 698 | (setf (js2-token-end (js2-current-token)) (1- (js2-current-token-end)) 699 | (js2-token-type (js2-current-token)) js2-SUB 700 | (js2-token-string (js2-current-token)) "-" 701 | js2-ts-cursor (1+ (js2-current-token-beg)) 702 | js2-ti-lookahead 0)) 703 | (push "-" name-parts)) 704 | (setq prev-token-end (js2-current-token-end)) 705 | (if (js2-match-token js2-NAME) 706 | (if (eq prev-token-end (js2-current-token-beg)) 707 | (progn (push (js2-current-token-string) name-parts) 708 | (setq prev-token-end (js2-current-token-end) 709 | name-start (or name-start (js2-current-token-beg)))) 710 | (js2-unget-token) 711 | (setq continue nil)) 712 | (when (= js2-COLON (js2-current-token-type)) 713 | (js2-report-error "msg.bad.jsx.ident" nil beg (- (js2-current-token-end) beg))) 714 | ;; We only keep going if this is an `ident-ending-with-dash-colon:' 715 | (setq continue (and (not matched-colon) (= (js2-peek-token) js2-COLON))))) 716 | (when face 717 | (js2-set-face beg (js2-current-token-end) face 'record)) 718 | (let ((name-node (if name-start 719 | (make-js2-name-node :pos name-start 720 | :len (- (js2-current-token-end) name-start) 721 | :name (apply #'concat (nreverse name-parts))) 722 | (make-js2-name-node :pos (js2-current-token-end) :len 0 :name "")))) 723 | (setf (js2-node-len pn) (- (js2-current-token-end) beg) 724 | (rjsx-identifier-name pn) name-node) 725 | (js2-node-add-children pn name-node)) 726 | pn) 727 | (make-js2-error-node :len (js2-current-token-len)))) 728 | 729 | (defun rjsx-parse-member-or-ns (&optional face) 730 | "Parse a dotted expression or a namespaced identifier and fontify with FACE if given." 731 | (let ((ident (rjsx-parse-identifier face))) 732 | (cond 733 | ((js2-error-node-p ident) ident) 734 | ((rjsx-identifier-namespace ident) ident) 735 | (t (rjsx-parse-member ident face))))) 736 | 737 | (defun rjsx-parse-member (ident &optional face) 738 | "Parse a dotted member expression starting with IDENT and fontify with FACE. 739 | IDENT is the `rjsx-identifier' node for the first item in the 740 | member expression. Returns a `js2-error-node' if unable to 741 | parse." 742 | (let (idents dots-pos pn end) 743 | (setq pn (make-rjsx-member :pos (js2-node-pos ident))) 744 | (setq end (js2-current-token-end)) 745 | (push ident idents) 746 | (while (and (js2-match-token js2-DOT) (not (js2-error-node-p ident))) 747 | (push (js2-current-token-beg) dots-pos) 748 | (setq end (js2-current-token-end)) 749 | (setq ident (rjsx-parse-identifier nil :allow-ns nil)) 750 | (push ident idents) 751 | (unless (js2-error-node-p ident) 752 | (setq end (js2-current-token-end))) 753 | (js2-node-add-children pn ident)) 754 | (apply 'js2-node-add-children pn idents) 755 | (setf (rjsx-member-idents pn) (nreverse idents) 756 | (rjsx-member-dots-pos pn) (nreverse dots-pos) 757 | (js2-node-len pn) (- end (js2-node-pos pn))) 758 | (when face 759 | (js2-set-face (js2-node-pos pn) end face 'record)) 760 | pn)) 761 | 762 | 763 | (defun rjsx-parse-child (expect-fragment) 764 | "Parse an XML child node. 765 | Child nodes include plain (unquoted) text, other XML elements, 766 | and {}-bracketed expressions. Return the parsed child. 767 | 768 | EXPECT-FRAGMENT if t, indicates that `' should be parsed 769 | as a fragment closing node, and not as an empty tag." 770 | (let ((tt (rjsx-get-next-xml-token))) 771 | (rjsx-maybe-message "child type `%s'" tt) 772 | (cond 773 | ((= tt js2-LT) 774 | (rjsx-maybe-message "xml-or-close") 775 | (rjsx-parse-xml-or-closing-tag expect-fragment)) 776 | 777 | ((= tt js2-LC) 778 | (rjsx-maybe-message "parsing expression { %s" (js2-peek-token)) 779 | (rjsx-parse-wrapped-expr t nil)) 780 | 781 | ((= tt rjsx-JSX-TEXT) 782 | (rjsx-maybe-message "text node: '%s'" (js2-current-token-string)) 783 | (js2-set-face (js2-current-token-beg) (js2-current-token-end) 'rjsx-text 'record) 784 | (js2-record-text-property (js2-current-token-beg) (js2-current-token-end) 785 | 'syntax-table rjsx-text-syntax-table) 786 | (make-rjsx-text :value (js2-current-token-string))) 787 | 788 | ((= tt js2-ERROR) 789 | (make-js2-error-node :len (js2-current-token-len))) 790 | 791 | (t (error "Unexpected token type: %s" (js2-peek-token)))))) 792 | 793 | (defun rjsx-parse-xml-or-closing-tag (expect-fragment) 794 | "Parse a JSX tag, which could be a child or a closing tag. 795 | Return the parsed child, which is a `rjsx-closing-tag' if a 796 | closing tag was parsed. 797 | 798 | EXPECT-FRAGMENT if t, indicates that `' should be parsed 799 | as a fragment closing node, and not as an empty tag." 800 | (let ((beg (js2-current-token-beg)) pn) 801 | (rjsx-record-token-class '<) 802 | (rjsx-record-tag-bracket-face) 803 | (if (and (not expect-fragment) (setq pn (rjsx-parse-empty-tag))) 804 | pn 805 | (if (js2-match-token js2-DIV) 806 | (progn (rjsx-record-tag-bracket-face) 807 | (if (and expect-fragment (eq (js2-peek-token) js2-GT)) 808 | (setq pn (make-rjsx-closing-tag :pos beg)) 809 | (setq pn (make-rjsx-closing-tag :pos beg 810 | :name (rjsx-parse-member-or-ns 'rjsx-tag))) 811 | (js2-node-add-children pn (rjsx-closing-tag-name pn))) 812 | (when (js2-must-match js2-GT "msg.no.gt.in.closer" beg (- (js2-current-token-end) beg)) 813 | (rjsx-record-token-class '>) 814 | (rjsx-record-tag-bracket-face)) 815 | (setf (js2-node-len pn) (- (js2-current-token-end) beg)) 816 | pn) 817 | (rjsx-maybe-message "parsing a child XML item") 818 | (rjsx-parse-xml))))) 819 | 820 | (defun rjsx-get-next-xml-token () 821 | "Scan through the XML text and push one token onto the stack." 822 | (setq js2-ts-string-buffer nil) ; for recording the text 823 | (when (> js2-ti-lookahead 0) 824 | (setq js2-ts-cursor (js2-current-token-end)) 825 | (setq js2-ti-lookahead 0)) 826 | 827 | (let ((token (js2-new-token 0)) 828 | c) 829 | (rjsx-maybe-message "Running the xml scanner") 830 | (catch 'return 831 | (while t 832 | (setq c (js2-get-char)) 833 | (rjsx-maybe-message "'%s' (%s)" (if (= c js2-EOF_CHAR) "EOF" (char-to-string c)) c) 834 | (cond 835 | ((or (= c ?}) (= c ?>)) 836 | (js2-set-string-from-buffer token) 837 | (setf (js2-token-type token) js2-ERROR) 838 | (js2-report-scan-error "msg.syntax" t) 839 | (throw 'return js2-ERROR)) 840 | 841 | ((or (= c ?<) (= c ?{)) 842 | (js2-unget-char) 843 | (if js2-ts-string-buffer 844 | (progn 845 | (js2-set-string-from-buffer token) 846 | (setf (js2-token-type token) rjsx-JSX-TEXT) 847 | (rjsx-maybe-message "created rjsx-JSX-TEXT token: `%s'" (js2-token-string token)) 848 | (throw 'return rjsx-JSX-TEXT)) 849 | (js2-get-char) 850 | (js2-set-string-from-buffer token) 851 | (setf (js2-token-type token) (if (= c ?<) js2-LT js2-LC)) 852 | (setf (js2-token-string token) (string c)) 853 | (throw 'return (js2-token-type token)))) 854 | 855 | ((= c js2-EOF_CHAR) 856 | (js2-set-string-from-buffer token) 857 | (rjsx-maybe-message "Hit EOF. Current buffer: `%s'" (js2-token-string token)) 858 | (setf (js2-token-type token) js2-ERROR) 859 | (rjsx-maybe-message "Scanner hit EOF. Panic!") 860 | (signal 'rjsx-eof-while-parsing nil)) 861 | (t (js2-add-to-string c))))))) 862 | 863 | (js2-deflocal rjsx-buffer-chars-modified-tick 0 "Variable holding the last per-buffer value of `buffer-chars-modified-tick'.") 864 | 865 | (defun rjsx-maybe-reparse () 866 | "Called before accessing the parse tree. 867 | For small buffers, will do an immediate reparse to ensure the 868 | parse tree is up to date." 869 | (when (and (<= (point-max) rjsx-max-size-for-frequent-reparse) 870 | (/= rjsx-buffer-chars-modified-tick (buffer-chars-modified-tick))) 871 | (js2-reparse) 872 | (setq rjsx-buffer-chars-modified-tick (buffer-chars-modified-tick)))) 873 | 874 | (defun rjsx--tag-at-point () 875 | "Return the JSX tag at point, if any, or nil." 876 | (rjsx-maybe-reparse) 877 | (let ((node (js2-node-at-point (point) t))) 878 | (while (and node (not (rjsx-node-p node))) 879 | (setq node (js2-node-parent node))) 880 | node)) 881 | 882 | 883 | ;;;; Interactive commands and keybindings 884 | (defun rjsx-electric-lt (n) 885 | "Insert a context-sensitive less-than sign. 886 | Optional prefix argument N indicates how many signs to insert. 887 | If N is greater than one, no special handling takes place. 888 | Otherwise, if the less-than sign would start a JSX block, it 889 | inserts `' and places the cursor inside the new tag." 890 | (interactive "p") 891 | (if (/= n 1) 892 | (insert (make-string n ?<)) 893 | (if (save-excursion 894 | (forward-comment most-negative-fixnum) 895 | (skip-chars-backward "\n\r") 896 | (or (= (point) (point-min)) 897 | (memq (char-before) (append "=(?:>}&|{," nil)) 898 | (let ((start (- (point) 6))) 899 | (and (>= start (point-min)) 900 | (string= (buffer-substring start (point)) "return"))))) 901 | (progn (insert "") 902 | (backward-char 2)) 903 | (insert "<")))) 904 | 905 | (define-key rjsx-mode-map "<" 'rjsx-electric-lt) 906 | 907 | (defun rjsx-expand-self-closing-tag (node) 908 | "Expand NODE into a balanced tag. 909 | Assumes NODE is self-closing `rjsx-node', and that point is at 910 | the self-closing slash." 911 | (delete-char 1) 912 | (search-forward ">") 913 | (save-excursion 914 | (insert ""))) 915 | 916 | (defun rjsx-electric-gt (n) 917 | "Insert a context-sensitive greater-than sign. 918 | Optional prefix argument N indicates how many signs to insert. 919 | If N is greater than one, no special handling takes place. 920 | Otherwise, if point is in a self-closing JSX tag just before the 921 | slash, it creates a matching end-tag and places point just inside 922 | the tags." 923 | (interactive "p") 924 | (if (or (/= n 1) 925 | (not (eq (get-char-property (point) 'rjsx-class) 'self-closing-slash))) 926 | (insert (make-string n ?>)) 927 | (let ((node (rjsx--tag-at-point))) 928 | (if node 929 | (rjsx-expand-self-closing-tag node) 930 | (insert (make-string n ?>)))))) 931 | 932 | (define-key rjsx-mode-map ">" 'rjsx-electric-gt) 933 | 934 | (defun rjsx-delete-creates-full-tag (n &optional killflag) 935 | "N and KILLFLAG are as in `delete-char'. 936 | If N is 1 and KILLFLAG nil, checks to see if we're in a 937 | self-closing tag about to delete the slash. If so, deletes the 938 | slash and inserts a matching end-tag." 939 | (interactive "p") 940 | (if (or killflag (/= 1 n) (not (eq (get-char-property (point) 'rjsx-class) 'self-closing-slash))) 941 | (if (called-interactively-p 'any) 942 | (call-interactively 'delete-forward-char) 943 | (delete-char n killflag)) 944 | (let ((node (rjsx--tag-at-point))) 945 | (if node 946 | (rjsx-expand-self-closing-tag node) 947 | (delete-char 1))))) 948 | 949 | (define-key rjsx-mode-map (kbd "C-d") 'rjsx-delete-creates-full-tag) 950 | 951 | (defun rjsx-rename-tag-at-point (new-name) 952 | "Prompt for a new name and modify the tag at point. 953 | NEW-NAME is the name to give the tag." 954 | (interactive "sNew tag name: ") 955 | (let* ((tag (rjsx--tag-at-point)) 956 | (closer (and tag (rjsx-node-closing-tag tag)))) 957 | (cond 958 | ((null tag) (message "No JSX tag found at point")) 959 | ((null (rjsx-node-name tag)) ; fragment 960 | (cl-assert closer nil "Fragment has no closing-tag") 961 | (save-excursion 962 | (goto-char (+ 2 (js2-node-abs-pos closer))) 963 | (insert new-name) 964 | (goto-char (1+ (js2-node-abs-pos tag))) 965 | (insert new-name)) 966 | (js2-reparse)) 967 | (t 968 | (let* ((head (rjsx-node-name tag)) 969 | (tail (when closer (rjsx-closing-tag-name closer))) 970 | beg end) 971 | (dolist (part (if tail (list tail head) (list head))) 972 | (setq beg (js2-node-abs-pos part) 973 | end (+ beg (js2-node-len part))) 974 | (delete-region beg end) 975 | (save-excursion (goto-char beg) (insert new-name))) 976 | (js2-reparse)))))) 977 | 978 | (define-key rjsx-mode-map (kbd "C-c C-r") 'rjsx-rename-tag-at-point) 979 | 980 | 981 | (defun rjsx-jump-closing-tag () 982 | "Go to closing tag of tag at point." 983 | (interactive) 984 | (let* ((tag (rjsx--tag-at-point)) 985 | (closer (and tag (rjsx-node-closing-tag tag)))) 986 | (cond 987 | ((null tag) (message "No JSX tag found at point")) 988 | ((null closer) (message "JSX tag is self closing")) 989 | (t 990 | (goto-char (+ 1 (js2-node-abs-pos closer))))))) 991 | 992 | 993 | (defun rjsx-jump-opening-tag () 994 | "Go to opening tag of tag at point." 995 | (interactive) 996 | (let ((tag (rjsx--tag-at-point))) 997 | (if (null tag) (message "No JSX tag found at point") 998 | (goto-char (+ 1 (js2-node-abs-pos tag)))))) 999 | 1000 | (defun rjsx-jump-tag () 1001 | "Switch between opening and closing tag of tag at point." 1002 | (interactive) 1003 | (let* ((tag (rjsx--tag-at-point)) 1004 | (closer (and tag (rjsx-node-closing-tag tag)))) 1005 | (cond 1006 | ((null tag) (message "No JSX tag found at point")) 1007 | ((null closer) (message "No closing JSX tag found at point")) 1008 | ((eq (line-number-at-pos (js2-node-abs-pos tag)) (line-number-at-pos)) (rjsx-jump-closing-tag)) 1009 | ((eq (line-number-at-pos (js2-node-abs-pos closer)) (line-number-at-pos)) (rjsx-jump-opening-tag)) 1010 | (t 1011 | (rjsx-jump-opening-tag))))) 1012 | 1013 | 1014 | 1015 | (define-key rjsx-mode-map (kbd "C-c C-j") 'rjsx-jump-tag) 1016 | 1017 | 1018 | 1019 | ;; Utilities 1020 | 1021 | (defun rjsx-ancestor (node predicate) 1022 | "Find an ancestor of NODE satisfying PREDICATE. 1023 | 1024 | Search upwards from the parent of NODE for an ancestor where 1025 | PREDICATE returns t. Returns nil if no ancestor satisfies 1026 | PREDICATE." 1027 | (let ((ancestor (js2-node-parent node))) 1028 | (while (and ancestor 1029 | (not (funcall predicate ancestor))) 1030 | (setq ancestor (js2-node-parent ancestor))) 1031 | ancestor)) 1032 | 1033 | (cl-defun rjsx--prev-sibling (node) 1034 | "Get the previous non-blank sibling of NODE." 1035 | (let* ((parent (js2-node-parent node)) prev) 1036 | (dolist (kid (rjsx-node-kids parent)) 1037 | (cond 1038 | ((eq kid node) (cl-return-from rjsx--prev-sibling prev)) 1039 | ((and (rjsx-text-p kid) (string-blank-p (rjsx-text-value kid)))) 1040 | (t (setq prev kid)))))) 1041 | 1042 | 1043 | 1044 | ;; Comment handling 1045 | 1046 | (defun rjsx-comment-region-function (beg end &optional arg) 1047 | (js2-mode-wait-for-parse 1048 | (lambda () 1049 | (let* ((node (js2-node-at-point beg)) 1050 | (in-jsx (or (rjsx-node-p node) 1051 | (rjsx-ancestor node 'rjsx-node-p))) 1052 | (use-jsx-comment (and (rjsx-node-p (js2-node-parent node)) 1053 | (or (rjsx-text-p node) 1054 | (and (rjsx-node-p node) 1055 | (= (js2-node-abs-pos node) beg)))))) 1056 | (cond (use-jsx-comment 1057 | (let ((comment-start "{/*") 1058 | (comment-end "*/}")) 1059 | (comment-normalize-vars) 1060 | (comment-region-default beg end arg))) 1061 | (in-jsx 1062 | (let ((comment-start "/*") 1063 | (comment-end "*/")) 1064 | (comment-normalize-vars) 1065 | (if (rjsx-wrapped-expr-p node) 1066 | (if (js2-empty-expr-node-p (rjsx-wrapped-expr-child node)) 1067 | (let ((comment-start "{/*") 1068 | (comment-end "*/}")) 1069 | (comment-normalize-vars) 1070 | (comment-region-default beg end arg)) 1071 | (comment-region-default (1+ beg) (1- end) arg)) 1072 | (comment-region-default beg end arg)))) 1073 | (t (comment-region-default beg end arg))))))) 1074 | 1075 | (defun rjsx-maybe-unwrap-expr (beg end) 1076 | (save-excursion 1077 | (save-restriction 1078 | (js2-reparse) 1079 | (goto-char beg) 1080 | (skip-chars-forward "[:space:]\n" end) 1081 | (let* ((node (js2-node-at-point (point))) 1082 | (parent (js2-node-parent node))) 1083 | (when (and parent 1084 | (rjsx-wrapped-expr-p parent) 1085 | (rjsx-node-p (js2-node-parent parent)) 1086 | (or (rjsx-text-p node) (rjsx-node-p node))) 1087 | (let* ((expr-start (js2-node-abs-pos parent)) 1088 | (expr-end (+ expr-start (js2-node-len parent))) 1089 | (body-start (1+ expr-start)) 1090 | (body-end (1- expr-end)) 1091 | (body-length (- body-end body-start))) 1092 | (when (and (comment-only-p body-start beg) 1093 | (comment-only-p end body-end)) 1094 | (goto-char expr-start) 1095 | (delete-char 1) 1096 | (forward-char body-length) 1097 | (delete-char 1)))))))) 1098 | 1099 | (defun rjsx-uncomment-region-function (beg end &optional _) 1100 | (js2-mode-wait-for-parse 1101 | (lambda () 1102 | (goto-char beg) 1103 | (setq end (copy-marker end)) 1104 | (let (cs ts te ce matched-start) 1105 | ;; find comment start 1106 | (while (and (<= (point) end) 1107 | (setq matched-start 1108 | (and (re-search-forward comment-start-skip end t 1) 1109 | (match-string-no-properties 0)))) 1110 | ;; delete comment-start 1111 | (setq cs (match-beginning 1)) 1112 | (setq ts (match-end 1)) 1113 | (goto-char cs) 1114 | (delete-region cs ts) 1115 | 1116 | ;; delete comment-padding start 1117 | (when (and comment-padding (looking-at (regexp-quote comment-padding))) 1118 | (delete-region (point) (+ (point) (length comment-padding)))) 1119 | 1120 | ;; find comment end 1121 | (when (re-search-forward (if (string-match "//+" matched-start) "\n" "\\*/}?") end t 1) 1122 | (setq te (or (match-beginning 1) (match-beginning 0))) 1123 | (setq ce (or (match-end 1) (match-end 0))) 1124 | (goto-char te) 1125 | 1126 | ;; delete commend-end if it's not a newline 1127 | (unless (string= "\n" (match-string-no-properties 0)) 1128 | (delete-region te ce) 1129 | 1130 | ;; delete comment-padding end 1131 | (when comment-padding 1132 | (backward-char (length comment-padding)) 1133 | (when (looking-at (regexp-quote comment-padding)) 1134 | (delete-region (point) (+ (point) (length comment-padding)))))) 1135 | 1136 | ;; unescape inner comments if any 1137 | (save-restriction 1138 | (narrow-to-region cs (point)) 1139 | (comment-quote-nested "{/*" "*/}" t))))) 1140 | 1141 | (rjsx-maybe-unwrap-expr beg end) 1142 | 1143 | (set-marker end nil)))) 1144 | 1145 | (defun rjsx-comment-quote-nested-function (_ __ unp) 1146 | (let ((re (concat "\\*\\(\\\\" (if unp "+" "*") "\\)/}?" 1147 | "\\|" 1148 | "{?/\\(\\\\" (if unp "+" "*") "\\)\\*"))) 1149 | (goto-char (point-min)) 1150 | (while (re-search-forward re (point-max) t 1) 1151 | (let ((ceme (match-end 1)) 1152 | (csme (match-end 2))) 1153 | (goto-char (or ceme csme)) 1154 | (if (and unp (>= (length (or (match-string-no-properties 1) 1155 | (match-string-no-properties 2))) 1156 | 1)) 1157 | (delete-char -1) 1158 | (insert "\\")))))) 1159 | 1160 | ;;;###autoload 1161 | (defun rjsx-comment-dwim (arg) 1162 | "RJSX implementation of `comment-dwim'. If called on a region, 1163 | this function delegates to `comment-or-uncomment-region'. If the 1164 | point is not in a JSX context, it delegates to the 1165 | `comment-dwim', otherwise it will comment the JSX AST node at 1166 | point using the apppriate comment delimiters. 1167 | 1168 | For example: If point is on a JSX attribute or JSX expression, it 1169 | will comment the entire attribute using \"/* */\". , otherwise if 1170 | it's on a descendent JSX Element, it will use \"{/* */}\" 1171 | instead." 1172 | (interactive "*P") 1173 | (js2-mode-wait-for-parse 1174 | (lambda () 1175 | (if (use-region-p) 1176 | (comment-or-uncomment-region (region-beginning) (region-end) arg) 1177 | (save-excursion 1178 | (when (looking-at "[[:space:]]") 1179 | (forward-whitespace 1)) 1180 | (let ((node (js2-node-at-point))) 1181 | (cond 1182 | ;; If inside a regular JS comment node, uncomment the node 1183 | ((js2-comment-at-point) 1184 | (uncomment-region (js2-node-abs-pos node) (js2-node-abs-end node))) 1185 | ;; If looking at a commented JSXAttribute value, forward one char to 1186 | ;; uncomment the body 1187 | ((and (looking-at comment-start-skip) 1188 | (looking-at "{") 1189 | (rjsx-attr-p (js2-node-parent node))) 1190 | (forward-char 1) 1191 | (rjsx-comment-dwim arg)) 1192 | ;; If the entire line is a comment, uncomment it 1193 | ((and (comment-only-p (point) (line-end-position)) 1194 | (not (looking-at "[[:space:]]*$"))) 1195 | (uncomment-region (point) (line-end-position))) 1196 | ;; If looking at JSXText, comment the current line 1197 | ((rjsx-text-p node) 1198 | (let ((comment-start "{/*") 1199 | (comment-end "*/}")) 1200 | (comment-line 1))) 1201 | ;; If looking at a JSXAttribute or a JSXSpreadAttribute, comment the 1202 | ;; entire attribute with C-style comment 1203 | ((or (rjsx-spread-p node) 1204 | (rjsx-ancestor node 'rjsx-spread-p) 1205 | (rjsx-attr-p node) 1206 | (and (js2-name-node-p node) 1207 | (rjsx-identifier-p (js2-node-parent node)) 1208 | (rjsx-attr-p (js2-node-parent (js2-node-parent node)))) 1209 | (and (rjsx-identifier-p node) 1210 | (rjsx-attr-p (js2-node-parent node))) 1211 | (and (js2-string-node-p node) 1212 | (rjsx-attr-p (js2-node-parent node)))) 1213 | (let ((node (or (and (rjsx-spread-p node) node) 1214 | (rjsx-ancestor node 'rjsx-spread-p) 1215 | (and (rjsx-attr-p node) node) 1216 | (rjsx-ancestor node 'rjsx-attr-p))) 1217 | (comment-start "/*") 1218 | (comment-end "*/")) 1219 | (comment-region 1220 | (js2-node-abs-pos node) 1221 | (js2-node-abs-end node) arg))) 1222 | ;; If looking at a JSXElement or JSXFragment, comment the entire 1223 | ;; node with JSX comment if it's a child of one of the above, 1224 | ;; otherwise just comment with C-style comment. 1225 | ((or (rjsx-node-p node) 1226 | (rjsx-closing-tag-p node) 1227 | (rjsx-member-p node) 1228 | (rjsx-ancestor node 'rjsx-member-p)) 1229 | (let* ((node (or (and (rjsx-node-p node) node) 1230 | (rjsx-ancestor node 'rjsx-node-p))) 1231 | (parent-node-p (rjsx-node-p (js2-node-parent node))) 1232 | (closing (rjsx-node-closing-tag node)) 1233 | (comment-start (if parent-node-p "{/*" "/*")) 1234 | (comment-end (if parent-node-p "*/}" "*/"))) 1235 | (comment-region 1236 | (js2-node-abs-pos node) 1237 | (js2-node-abs-end (or closing node)) arg))) 1238 | ;; If looking at a JSX {expression} or is inside a JSX expression, 1239 | ;; comment the body with regular C-style comment. If the body is 1240 | ;; already commented, uncomment it. If on a multi line JSX 1241 | ;; expression, just comment the line. 1242 | ((or (rjsx-wrapped-expr-p node) 1243 | (rjsx-ancestor node 'rjsx-wrapped-expr-p)) 1244 | (let* ((expr (or (and (rjsx-wrapped-expr-p node) node) 1245 | (rjsx-ancestor node 'rjsx-wrapped-expr-p))) 1246 | ;; Can only happen as a child of an element or fragment, an 1247 | ;; empty JSX expression attribute value will result in an 1248 | ;; error node 1249 | (expr-empty (js2-empty-expr-node-p (rjsx-wrapped-expr-child expr))) 1250 | (body-start (1+ (js2-node-abs-pos expr))) 1251 | (body-end (1- (js2-node-abs-end expr))) 1252 | (expr-child-of-node (rjsx-node-p (js2-node-parent expr)))) 1253 | ;; If the body is all comment, uncomment it, otherwise if it's 1254 | ;; empty, wrap it with a JSX comment 1255 | (if (and expr-child-of-node expr-empty) 1256 | (if (or (= (1+ (js2-node-abs-pos expr)) (js2-node-abs-end expr)) ;; {} 1257 | (string-blank-p (buffer-substring-no-properties body-start body-end))) 1258 | (let ((comment-start "{/*") 1259 | (comment-end "*/}")) 1260 | (comment-region (js2-node-abs-pos expr) (js2-node-abs-end expr) arg)) 1261 | (when (comment-only-p body-start body-end) 1262 | (uncomment-region body-start body-end arg))) 1263 | ;; If in a multi-line JSX expression, comment the line 1264 | (if (> (count-lines body-start body-end) 1) 1265 | (comment-dwim arg) 1266 | ;; Otherwise comment the entire body 1267 | (let ((comment-start "/*") 1268 | (comment-end "*/")) 1269 | (comment-region body-start body-end arg)))))) 1270 | ;; Otherwise just delegate to (comment-dwim) 1271 | (t (comment-dwim arg))))))))) 1272 | 1273 | (define-key rjsx-mode-map [remap comment-dwim] 'rjsx-comment-dwim) 1274 | 1275 | 1276 | 1277 | ;; Indentation 1278 | 1279 | (defun rjsx--js--looking-at-operator-p-advice (orig-fun) 1280 | "Advice for `js--looking-at-operator-p' (ORIG-FUN) to handle JSX properly." 1281 | (if (memq (get-char-property (point) 'rjsx-class) '(< >)) 1282 | nil 1283 | (apply orig-fun nil))) 1284 | 1285 | (advice-add 'js--looking-at-operator-p :around #'rjsx--js--looking-at-operator-p-advice) 1286 | 1287 | (defvar-local rjsx--indent-region-p nil 1288 | "t when `indent-region' is running.") 1289 | 1290 | (defvar-local rjsx--indent-running-offset 0 1291 | "Running offset to add to the node POS at the beginning of 1292 | every line during `indent-region' so we don't have to reparse 1293 | after indenting every line to update the AST node positions.") 1294 | 1295 | (defvar-local rjsx--node-abs-pos-cache nil 1296 | "Cache for JSX nodes' indent levels. 1297 | Used during `indent-region' calls to avoid repeated 1298 | `js2-node-abs-pos' calls.") 1299 | 1300 | (defun rjsx--node-abs-pos (node) 1301 | "Caching version of `js2-node-abs-pos' for NODE." 1302 | (if (null rjsx--node-abs-pos-cache) 1303 | (js2-node-abs-pos node) 1304 | (let ((cached (gethash node rjsx--node-abs-pos-cache))) 1305 | (or cached 1306 | (setf (gethash node rjsx--node-abs-pos-cache) 1307 | (js2-node-abs-pos node)))))) 1308 | 1309 | (defsubst rjsx--node-indent-level (node) 1310 | "Return the indent level for NODE." 1311 | (save-excursion (goto-char (rjsx--node-abs-pos node)) (current-column))) 1312 | 1313 | (defsubst rjsx--indent-line-to-offset (node offset) 1314 | "Indent current line relative to NODE, which must be an `rjsx-node' instance. 1315 | OFFSET indicates the number of spaces to add." 1316 | (indent-line-to 1317 | (+ offset (rjsx--node-indent-level node)))) 1318 | 1319 | (defun rjsx-indent-line () 1320 | "Similar to `js-jsx-indent-line', but fixes indentation bug 1321 | with JSXElement after a JSX expression and arrow function. In 1322 | addition, the > of a multi-line open tag and the /> of a 1323 | multi-line self-closing tag are aligned to the beginning <. This 1324 | function also ensures everything inside a JSX context is indented 1325 | according to `js-indent-level' using spaces, this is due to the 1326 | limitation of `sgml-indent-line' not being able to indent with 1327 | tabs. 1328 | 1329 | Fixes: 1330 | - https://github.com/mooz/js2-mode/issues/490 1331 | - https://github.com/mooz/js2-mode/issues/482 1332 | - https://github.com/mooz/js2-mode/issues/462 1333 | - https://github.com/mooz/js2-mode/issues/451 1334 | " 1335 | (unless rjsx--indent-region-p 1336 | (js2-reparse)) 1337 | (let ((delta (- (point) (progn (back-to-indentation) (point))))) 1338 | (rjsx--indent-line-1) 1339 | (back-to-indentation) 1340 | (when (> delta 0) 1341 | (forward-char delta)))) 1342 | 1343 | (defun rjsx--indent-line-1 () 1344 | "Helper for `rjsx-indent-line'." 1345 | (let* ((indent-tabs-mode nil) 1346 | (cur-pos (point)) 1347 | (cur-char (char-after cur-pos)) 1348 | (node (js2-node-at-point (- cur-pos rjsx--indent-running-offset))) 1349 | (parent (js2-node-parent node))) 1350 | (cond 1351 | ((rjsx-node-p node) 1352 | (cond 1353 | ((eq cur-char ?<) 1354 | (if (rjsx-node-p parent) 1355 | (rjsx--indent-line-to-offset parent sgml-basic-offset) 1356 | ;; Top-level node, indent as JS 1357 | (js-indent-line)) 1358 | (when rjsx--node-abs-pos-cache 1359 | (setf (gethash node rjsx--node-abs-pos-cache) 1360 | (save-excursion (back-to-indentation) (point))))) 1361 | ((memq cur-char '(?/ ?>)) 1362 | (rjsx--indent-line-to-offset node 0)) 1363 | ((eq cur-char ?\n) 1364 | (rjsx--indent-line-to-offset node sgml-basic-offset)) 1365 | (t (error "Don't know how to indent %s for JSX node" (make-string 1 cur-char))))) 1366 | ((and (rjsx-identifier-p parent) 1367 | (rjsx-member-p (js2-node-parent parent)) 1368 | (rjsx-node-p (js2-node-parent (js2-node-parent parent)))) 1369 | (rjsx--indent-line-to-offset (js2-node-parent (js2-node-parent parent)) 0)) 1370 | 1371 | ;; JSX children 1372 | ((rjsx-closing-tag-p node) 1373 | (rjsx--indent-line-to-offset parent 0)) 1374 | ((rjsx-text-p node) 1375 | (rjsx--indent-line-to-offset parent sgml-basic-offset)) 1376 | ((rjsx-wrapped-expr-p node) 1377 | (if (eq cur-char ?}) 1378 | (js-indent-line) 1379 | (rjsx--indent-line-to-offset parent sgml-basic-offset))) 1380 | 1381 | ;; Attribute-like (spreads, attributes, etc.) 1382 | ;; if first attr is on same line as tag, then align 1383 | ;; otherwise indent to parent level + sgml-basic-offset 1384 | ((or (rjsx-identifier-p node) 1385 | (and (rjsx-identifier-p parent) 1386 | (rjsx-attr-p (js2-node-parent parent))) 1387 | (rjsx-spread-p node)) 1388 | (let* ((tag (or (rjsx-ancestor node #'rjsx-node-p) 1389 | (error "Did not find containing JSX tag for attributes"))) 1390 | (name (rjsx-node-name tag)) 1391 | column) 1392 | (save-excursion 1393 | (goto-char (rjsx--node-abs-pos tag)) 1394 | (setq column (current-column)) 1395 | (when name (forward-char (js2-node-end name)) (skip-chars-forward " \t")) 1396 | (if (eolp) 1397 | (setq column (+ column sgml-basic-offset sgml-attribute-offset)) 1398 | (setq column (current-column)))) 1399 | (indent-line-to column))) 1400 | 1401 | ;; Everything else indent as javascript 1402 | (t (js-indent-line))) 1403 | 1404 | (when rjsx--indent-region-p 1405 | (cl-incf rjsx--indent-running-offset 1406 | (- (save-excursion (back-to-indentation) (point)) 1407 | cur-pos))))) 1408 | 1409 | (defun rjsx-indent-region (start end) 1410 | "Indent the region from START to END as JS/JSX." 1411 | (js2-reparse) 1412 | (let ((rjsx--indent-region-p t) 1413 | (rjsx--indent-running-offset 0) 1414 | (rjsx--node-abs-pos-cache (make-hash-table))) 1415 | (js2-indent-region start end))) 1416 | 1417 | 1418 | (provide 'rjsx-mode) 1419 | ;;; rjsx-mode.el ends here 1420 | 1421 | ;; Local Variables: 1422 | ;; outline-regexp: ";;;\\(;* [^ 1423 | ;; ]\\|###autoload\\)\\|(....." 1424 | ;; End: 1425 | -------------------------------------------------------------------------------- /rjsx-tests.el: -------------------------------------------------------------------------------- 1 | ;;; rjsx-tests.el --- Tests for rjsx-mode. -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2016 Felipe Ochoa 4 | 5 | ;;;; Commentary: 6 | 7 | ;; 8 | 9 | ;;;; Code: 10 | 11 | (load-file "./js2-tests.el") 12 | (require 'rjsx-mode) 13 | (require 'cl-lib) 14 | (require 'ert) 15 | (require 'ert-x) 16 | 17 | (defun js2-mode--and-parse () ;; No point in advising, so we just overwrite this internal function 18 | (rjsx-mode) 19 | (js2-reparse)) 20 | 21 | (js2-deftest-parse no-attr-no-children-self-closing 22 | "
;") 23 | 24 | (js2-deftest-parse no-attr-no-children-self-closing 25 | "
;") 26 | 27 | (js2-deftest-parse empty-attr-no-children 28 | "
;") 29 | 30 | (js2-deftest-parse no-children-self-closing 31 | "
;") 32 | 33 | (js2-deftest-parse no-attr-xml-child 34 | "
;") 35 | 36 | (js2-deftest-parse no-attr-text-child 37 | "
Hello world
;") 38 | 39 | (js2-deftest-parse no-attr-expr-child 40 | "let coolVar = false;\n
{coolVar ? 'abc' : 'xyz'}
;") 41 | 42 | (js2-deftest-parse ultra-nested 43 | "let fall = window.prompt('Fall?');\n
{fall ? Fell : }}>
;") 44 | 45 | (js2-deftest-parse hidden-behind-and 46 | "let cond = true;\n
{cond && }
;") 47 | 48 | (js2-deftest-parse ns-tag 49 | ";") 50 | 51 | (js2-deftest-parse ns-tag-with-dashes 52 | ";") 53 | 54 | (js2-deftest-parse ns-tag-with-dashes-at-end 55 | ";") 56 | 57 | (js2-deftest-parse ns-attr 58 | ";") 59 | 60 | (js2-deftest-parse ns-attr-with-dashes-at-end 61 | ";") 62 | 63 | (js2-deftest-parse ns-attr-with-dashes-at-end-of-ns 64 | ";") 65 | 66 | (js2-deftest-parse fragment 67 | "<>;") 68 | 69 | (js2-deftest-parse member-tag 70 | ";" 71 | :bind (js2-highlight-external-variables)) 72 | 73 | (js2-deftest-parse member-tag-many 74 | ";" 75 | :bind (js2-highlight-external-variables)) 76 | 77 | (js2-deftest-parse empty-attr-self-closing 78 | ";" 79 | :bind (js2-highlight-external-variables)) 80 | 81 | (js2-deftest-parse empty-attr-at-end 82 | "Hi;" 83 | :bind (js2-highlight-external-variables)) 84 | 85 | (js2-deftest-parse empty-attr-in-middle 86 | ";" 87 | :bind (js2-highlight-external-variables)) 88 | 89 | (js2-deftest-parse complex 90 | "
91 | this._topInput = c}/> 96 | {errors.name && {errors.name}} 97 | { } Empty is OK as child, but warning is issued 98 | 99 | {/* Node with comment gets no warning */} 100 | hello
This should be a spread, so error 101 |
Empty attributes are not allowed 102 | {React.Children.count(this.props.children) === 1 103 | ? {this.props.children} 104 | : React.Children.map(this.props.children, (child, index) => ( 105 |
  • 106 | {index === 0 && } 107 | {React.cloneElement(child, {toolTip: })} 108 |
  • 109 | )) 110 | } 111 | " 112 | :errors-count 2 113 | :warnings-count 2 114 | :syntax-error "{ }") 115 | 116 | (js2-deftest-parse empty-child 117 | "
    {}
    ;" 118 | :warnings-count 1) 119 | 120 | (js2-deftest-parse empty-child-with-comment 121 | "
    {/* this is a comment */}
    ;" 122 | :warnings-count 0 123 | :reference "
    {}
    ;") 124 | 125 | (js2-deftest-parse jsx-no-side-effects 126 | "function abc() {\n
    ;\n}" 127 | :warnings-count 1) 128 | 129 | (js2-deftest-parse undeclared-component 130 | "const C = function() { return ;\n};" 131 | :warnings-count 1) 132 | 133 | (js2-deftest-parse undeclared-component-lowercase-ok 134 | "const C = function() { return ;\n};" 135 | :warnings-count 0) 136 | 137 | (ert-deftest rjsx-syntax-table-in-text () 138 | "Ensure JSX text sequences use the default syntax table." 139 | (ert-with-test-buffer (:name 'rjsx) 140 | (insert "const d =
    {/* this is a comment */}This is") 141 | (save-excursion (insert "' text{ anExpression }
    ;")) 142 | (setq parse-sexp-lookup-properties t) 143 | (js2-mode--and-parse) 144 | (should (eq (syntax-class (syntax-after (point))) (syntax-class (string-to-syntax ".")))))) 145 | 146 | (defun rjsx-serialize-ast () 147 | "Return a streamlined ast representation." 148 | (let ((stack '(()))) 149 | (js2-visit-ast 150 | js2-mode-ast 151 | (lambda (node end-p) 152 | (if end-p 153 | (push (nreverse (pop stack)) (car stack)) 154 | (push (list (js2-node-len node) 155 | (js2-node-pos node) 156 | (js2-node-short-name node)) 157 | stack)))) 158 | (caar stack))) 159 | 160 | (ert-deftest rjsx-jsx-tag-names-ast () 161 | "Regression test for #54. Ensure the ast is properly built." 162 | (ert-with-test-buffer (:name 'rjsx) 163 | (insert "import Comp from 'abc';\n\nconst c = () => (\n Child\n);") 164 | (js2-mode--and-parse) 165 | (should (equal (rjsx-serialize-ast) 166 | '("js2-ast-root" 1 66 167 | ("js2-import-node" 0 23 168 | ("js2-import-clause-node" 0 11 169 | ("js2-export-binding-node" 7 4 170 | ("js2-name-node" 0 4))) 171 | ("js2-from-clause-node" 12 10)) 172 | ("js2-expr-stmt-node" 25 41 173 | ("js2-var-decl-node" 0 40 174 | ("js2-var-init-node" 6 34 175 | ("js2-name-node" 0 1) 176 | ("js2-function-node" 4 30 177 | ("js2-paren-node" 6 24 178 | ("rjsx-node" 4 18 179 | ("rjsx-member" 1 4 180 | ("rjsx-identifier" 0 4 181 | ("js2-name-node" 0 4))) 182 | ("rjsx-text" 6 5) 183 | ("rjsx-closing-tag" 11 7 184 | ("rjsx-member" 2 4 185 | ("rjsx-identifier" 0 4 186 | ("js2-name-node" 0 4))))))))))))))) 187 | 188 | (ert-deftest rjsx-jsx-self-closing-tag-names-ast () 189 | "Regression test for #54. Ensure the ast is properly built." 190 | (ert-with-test-buffer (:name 'rjsx) 191 | (insert "import Component from 'abc';\n\nconst c = () => (\n \n);") 192 | (js2-mode--and-parse) 193 | (should (equal (rjsx-serialize-ast) 194 | '("js2-ast-root" 1 65 195 | ("js2-import-node" 0 28 196 | ("js2-import-clause-node" 0 16 197 | ("js2-export-binding-node" 7 9 198 | ("js2-name-node" 0 9))) 199 | ("js2-from-clause-node" 17 10)) 200 | ("js2-expr-stmt-node" 30 35 201 | ("js2-var-decl-node" 0 34 202 | ("js2-var-init-node" 6 28 203 | ("js2-name-node" 0 1) 204 | ("js2-function-node" 4 24 205 | ("js2-paren-node" 6 18 206 | ("rjsx-node" 4 12 207 | ("rjsx-member" 1 9 208 | ("rjsx-identifier" 0 9 209 | ("js2-name-node" 0 9)))))))))))))) 210 | 211 | (ert-deftest rjsx-jsx-attr-pos-ast () 212 | "Regression test for #54. Ensure the ast is properly built." 213 | (ert-with-test-buffer (:name 'rjsx) 214 | (insert "import Comp from 'abc';\n\nconst c = () => (\n Child\n);") 215 | 216 | (js2-mode--and-parse) 217 | (should (equal (rjsx-serialize-ast) 218 | '("js2-ast-root" 1 80 219 | ("js2-import-node" 0 23 220 | ("js2-import-clause-node" 0 11 221 | ("js2-export-binding-node" 7 4 222 | ("js2-name-node" 0 4))) 223 | ("js2-from-clause-node" 12 10)) 224 | ("js2-expr-stmt-node" 25 55 225 | ("js2-var-decl-node" 0 54 226 | ("js2-var-init-node" 6 48 227 | ("js2-name-node" 0 1) 228 | ("js2-function-node" 4 44 229 | ("js2-paren-node" 6 38 230 | ("rjsx-node" 4 32 231 | ("rjsx-member" 1 4 232 | ("rjsx-identifier" 0 4 233 | ("js2-name-node" 0 4))) 234 | ("rjsx-attr" 6 5 235 | ("rjsx-identifier" 0 1 236 | ("js2-name-node" 0 1)) 237 | ("js2-string-node" 2 3)) 238 | ("rjsx-spread" 12 7 239 | ("js2-object-node" 4 2)) 240 | ("rjsx-text" 20 5) 241 | ("rjsx-closing-tag" 25 7 242 | ("rjsx-member" 2 4 243 | ("rjsx-identifier" 0 4 244 | ("js2-name-node" 0 4))))))))))))))) 245 | 246 | ;;; Now we test all of the malformed bits: 247 | 248 | (defun jsx-test--forms (forms) 249 | "Test the parsing of FORMS. 250 | FORMS must be a list of (CODE-STRING SYNTAX-ERROR &optional ERRORS-COUNT) 251 | forms, where the values are as in `js2-parse-string'." 252 | (ert-with-test-buffer (:name 'origin) 253 | (dolist (form forms) 254 | (cl-destructuring-bind (code-string syntax-error &optional (errors-count 1)) form 255 | (erase-buffer) 256 | (let* ((ast (js2-test-string-to-ast code-string)) 257 | (errors (js2-ast-root-errors ast))) 258 | (should (= errors-count (length errors))) 259 | (cl-destructuring-bind (_ pos len) (car (last errors)) 260 | (should (string= syntax-error (substring code-string 261 | (1- pos) (+ pos len -1)))))) 262 | (message (format "Completed test for: %s" code-string)))))) 263 | 264 | (cl-defmacro jsx-deftest (name &rest forms) 265 | "Define a new test for compactly checking multiple strings' parsing. 266 | The test is named rjsx-NAME. FORMS is a list of (CODE-STRING 267 | SYNTAX-ERROR ERRORS-COUNT) forms, where the values are as in 268 | `js2-parse-string'. `:expect-fail' can be inserted anywhere between 269 | between the forms to split the test into two: forms before the marker 270 | are expected to pass and forms after the marker are expected to fail. 271 | Currently only forms with syntax errors are supported. 272 | 273 | \(fn NAME PASS-FORMS... [:expect-fail FAIL-FORMS...])" 274 | (declare (indent defun)) 275 | (let (fail-forms pass-forms found-marker) 276 | (dolist (form forms) 277 | (if (eq :expect-fail form) 278 | (if found-marker 279 | (error "Received multiple :expect-fail markers") 280 | (setq found-marker t)) 281 | (if found-marker 282 | (push form fail-forms) 283 | (push form pass-forms)))) 284 | (setq fail-forms (nreverse fail-forms) 285 | pass-forms (nreverse pass-forms)) 286 | (let* ((i 0) 287 | (tests (nconc 288 | (and pass-forms 289 | (list `(ert-deftest ,(intern (format "rjsx-%s" name)) () 290 | (jsx-test--forms ',pass-forms)))) 291 | (mapcar 292 | (lambda (form) 293 | `(ert-deftest ,(intern (format "rjsx-%s-expected-fail-%d" name (cl-incf i))) () 294 | :expected-result :failed 295 | (jsx-test--forms '(,form)))) 296 | fail-forms)))) 297 | (cl-case (length tests) 298 | (0 (error "Did not specify any forms")) 299 | (1 (car tests)) 300 | (t `(progn ,@tests)))))) 301 | 302 | (defvar rjsx--find-test-regexp 303 | (concat "^\\s-*(rjsx-deftest" 304 | find-function-space-re 305 | "%s\\(\\s-\\|$\\)") 306 | "The regexp the `find-function' mechanisms use for finding RJSX test definitions.") 307 | 308 | (push '(rjsx-deftest . rjsx--find-test-regexp) find-function-regexp-alist) 309 | 310 | (defun rjsx-find-test-advice (orig-fn test-name) 311 | "Advice for `ert-find-test-other-window' (ORIG-FN) to find TEST-NAME." 312 | (interactive (list (ert-read-test-name-at-point "Find test definition: "))) 313 | (if (string-match "^rjsx-\\([a-z-]+?\\)\\(-expected-fail\\)?$" (symbol-name test-name)) 314 | (let* ((file (symbol-file test-name 'ert-deftest)) 315 | (buffer-point (find-definition-noselect (intern (match-string 1 (symbol-name test-name))) 316 | 'rjsx-deftest 317 | file)) 318 | (switch-fn 'switch-to-buffer-other-window) 319 | ;; The rest of this let-form was copy-pasted from 320 | ;; `find-funciton-do-it' to be able to override the 321 | ;; buffer-finding bit 322 | (orig-point (point)) 323 | (orig-buffers (buffer-list)) 324 | ;(buffer-point (save-excursion (find-definition-noselect symbol type))) 325 | (new-buf (car buffer-point)) 326 | (new-point (cdr buffer-point))) 327 | (when buffer-point 328 | (when (memq new-buf orig-buffers) 329 | (push-mark orig-point)) 330 | (funcall switch-fn new-buf) 331 | (when new-point (goto-char new-point)) 332 | (recenter find-function-recenter-line) 333 | (run-hooks 'find-function-after-hook))) 334 | (funcall orig-fn test-name))) 335 | 336 | (advice-add 'ert-find-test-other-window :around #'rjsx-find-test-advice) 337 | 338 | 339 | ;; Tag problems 340 | 341 | (jsx-deftest mismatched-tags 342 | ("
    " "") 343 | ("" "") 344 | ("
    " "
    ") 345 | ("" "") 346 | ("
    " "
    ") 347 | ("" "") 348 | ("" "") 349 | ("" "")) 350 | 351 | (js2-deftest-parse invalid-tag-member-and-ns-self-closing 352 | "" 353 | :syntax-error ".") ;; TODO: report the error over the entire tag 354 | 355 | (js2-deftest-parse invalid-ns-tag-with-double-dashes 356 | ";" 357 | :syntax-error "--") ;; TODO: report the error over the entire tag 358 | 359 | (js2-deftest-parse invalid-tag-whitespace-before-dash 360 | "
    " 361 | :syntax-error "-") 362 | 363 | (js2-deftest-parse missing-closing-lt-self-closing 364 | "
    " 369 | :errors-count 2 370 | :syntax-error "123") 371 | 372 | (js2-deftest-parse invalid-tag-name-only-ns 373 | "" 374 | :syntax-error "abc:") 375 | 376 | (js2-deftest-parse invalid-attr-name-only-ns 377 | "" 378 | :syntax-error "abc:") 379 | 380 | ;; Make sure we don't hang with unclosed tags 381 | 382 | (js2-deftest-parse falls-off-a-cliff-but-doesnt-hang 383 | "const Component = ({prop}) => ;\n\nexport default Component;" 384 | :syntax-error ";\n\nexport default Component;") 385 | 386 | (js2-deftest-parse falls-off-a-cliff-but-doesnt-hang-even-with-braces 387 | "const Component = ({prop}) => ;\n\nexport { Component };" 388 | :syntax-error ";\n\nexport { Component };") 389 | 390 | (js2-deftest-parse falls-off-a-cliff-but-doesnt-hang-even-with-other-jsx 391 | "const Component = ({prop}) => ;\nconst C2 = () => \n\n" 392 | :errors-count 2 ; 1 from the stray > in the arrow function and 1 from the missing closer 393 | :syntax-error ";\nconst C2 = () =>") 394 | 395 | (js2-deftest-parse falls-off-a-cliff-in-recursive-parse 396 | "const Component = ({prop}) =>
    {pred && };\n\nexport { Component }" 397 | :errors-count 2 398 | :syntax-error "}") 399 | 400 | (jsx-deftest empty-tag-encounter-survived 401 | ("" "<") 402 | ("
    " "<") 403 | ("
    {a && }
    " "<")) 404 | 405 | ;; Malformed attributes have a number of permutations: 406 | ;; 407 | ;; A/ Missing value, missing right curly, bad expression 408 | ;; B/ Before another attribute (spread vs expr vs string) or 409 | ;; at the end of the tag (self-closing or not) 410 | ;; C/ No dashes in its name, ends in a dash, dashes but not at the end 411 | ;; D/ With a namespaced name or not 412 | ;; 413 | ;; Combinatorial explosion! 3 * 5 * 3 * 2 = 90! 414 | 415 | ;; Here are all the missing values sign tests: 416 | (jsx-deftest attr-missing-value-no-dashes 417 | ("
    " "attr=") 418 | ("
    " "attr=") 419 | ("
    " "attr=") 420 | :expect-fail 421 | ("
    " "attr=") ; spread parsed as attribute value 422 | ("
    " "attr=")) ; JS2 parses the => as an arrow 423 | 424 | (jsx-deftest attr-missing-value-ends-in-dash 425 | ("
    " "attr-=") 426 | ("
    " "attr-=") 427 | ("
    " "attr-=") 428 | :expect-fail 429 | ("
    " "attr-=") 430 | ("
    " "attr-=")) 431 | 432 | (jsx-deftest attr-missing-value-interior-dashes 433 | ("
    " "attr-name=") 434 | ("
    " "attr-name=") 435 | ("
    " "attr-name=") 436 | :expect-fail 437 | ("
    " "attr-name=") 438 | ("
    " "attr-name=")) 439 | 440 | (jsx-deftest attr-missing-value-no-dashes-namespaced 441 | ("
    " "ns:attr=") 442 | ("
    " "ns:attr=") 443 | ("
    " "ns:attr=") 444 | :expect-fail 445 | ("
    " "ns:attr=") 446 | ("
    " "ns:attr=")) 447 | 448 | (jsx-deftest attr-missing-value-ends-in-dash-namespaced 449 | ("
    " "ns:attr-=") 450 | ("
    " "ns:attr-=") 451 | ("
    " "ns:attr-=") 452 | :expect-fail 453 | ("
    " "ns:attr-=") 454 | ("
    " "ns:attr-=")) 455 | 456 | (jsx-deftest attr-missing-value-interior-dashes-namespaced 457 | ("
    " "ns:attr-name=") 458 | ("
    " "ns:attr-name=") 459 | ("
    " "ns:attr-name=") 460 | :expect-fail 461 | ("
    " "ns:attr-name=") 462 | ("
    " "ns:attr-name=")) 463 | 464 | ;; Missing right curly in attribute 465 | (jsx-deftest attr-missing-rc-no-dashes 466 | :expect-fail 467 | ("
    " "attr={123") 468 | ("
    " "attr={123") 469 | ("
    " "attr={123") 470 | ("
    " "attr={123") 471 | ("
    " "attr={123")) 472 | 473 | (jsx-deftest attr-missing-rc-ends-in-dash 474 | :expect-fail 475 | ("
    " "attr-={123") 476 | ("
    " "attr-={123") 477 | ("
    " "attr-={123") 478 | ("
    " "attr-={123") 479 | ("
    " "attr-={123")) 480 | 481 | (jsx-deftest attr-missing-rc-interior-dashes 482 | :expect-fail 483 | ("
    " "attr-name={123") 484 | ("
    " "attr-name={123") 485 | ("
    " "attr-name={123") 486 | ("
    " "attr-name={123") 487 | ("
    " "attr-name={123")) 488 | 489 | (jsx-deftest attr-missing-rc-no-dashes-namespaced 490 | :expect-fail 491 | ("
    " "ns:attr={123") 492 | ("
    " "ns:attr={123") 493 | ("
    " "ns:attr={123") 494 | ("
    " "ns:attr={123") 495 | ("
    " "ns:attr={123")) 496 | 497 | (jsx-deftest attr-missing-rc-ends-in-dash-namespaced 498 | :expect-fail 499 | ("
    " "ns:attr-={123") 500 | ("
    " "ns:attr-={123") 501 | ("
    " "ns:attr-={123") 502 | ("
    " "ns:attr-={123") 503 | ("
    " "ns:attr-={123")) 504 | 505 | (jsx-deftest attr-missing-rc-interior-dashes-namespaced 506 | :expect-fail 507 | ("
    " "ns:attr-name={123") 508 | ("
    " "ns:attr-name={123") 509 | ("
    " "ns:attr-name={123") 510 | ("
    " "ns:attr-name={123") 511 | ("
    " "ns:attr-name={123")) 512 | 513 | 514 | ;; Here are all the bad values sign tests: 515 | (jsx-deftest attr-bad-value-no-dashes 516 | ("
    " "{&&}") 517 | ("
    " "{&&}") 518 | ("
    " "{&&}") 519 | ("
    " "{&&}") 520 | ("
    " "{&&}")) 521 | 522 | (jsx-deftest attr-bad-value-ends-in-dash 523 | ("
    " "{&&}") 524 | ("
    " "{&&}") 525 | ("
    " "{&&}") 526 | ("
    " "{&&}") 527 | ("
    " "{&&}")) 528 | 529 | (jsx-deftest attr-bad-value-interior-dashes 530 | ("
    " "{&&}") 531 | ("
    " "{&&}") 532 | ("
    " "{&&}") 533 | ("
    " "{&&}") 534 | ("
    " "{&&}")) 535 | 536 | (jsx-deftest attr-bad-value-no-dashes-namespaced 537 | ("
    " "{&&}") 538 | ("
    " "{&&}") 539 | ("
    " "{&&}") 540 | ("
    " "{&&}") 541 | ("
    " "{&&}")) 542 | 543 | (jsx-deftest attr-bad-value-ends-in-dash-namespaced 544 | ("
    " "{&&}") 545 | ("
    " "{&&}") 546 | ("
    " "{&&}") 547 | ("
    " "{&&}") 548 | ("
    " "{&&}")) 549 | 550 | (jsx-deftest attr-bad-value-interior-dashes-namespaced 551 | ("
    " "{&&}") 552 | ("
    " "{&&}") 553 | ("
    " "{&&}") 554 | ("
    " "{&&}") 555 | ("
    " "{&&}")) 556 | 557 | ;; Invalid jsx-strings 558 | 559 | (js2-deftest-parse invalid-jsx-string-in-attr 560 | "
    " 561 | :syntax-error "\"He said, \\\"Don't you worry child\\\"\"") 562 | 563 | (js2-deftest-parse invalid-jsx-string-in-attr-single-quotes 564 | "
    " 565 | :syntax-error "'He said, \"Don\\'t you worry child\"'") 566 | 567 | ;; Spread-specific errors also have some combinatorial complexity: 568 | ;; A/ Missing value or bad value or good value 569 | ;; B/ With or without dots 570 | ;; C/ With or without right curly (except if good value and dots are there) 571 | ;; D/ Before another attribute (spread vs expr vs string) or 572 | ;; at the end of the tag (self-closing or not) 573 | ;; 574 | ;; Total = (3 * 2 * 2 - 1) * 5 = 55 575 | 576 | (jsx-deftest spread-no-value-no-dots-no-rc 577 | :expect-fail 578 | ("
    " "{") 579 | ("
    " "{") 580 | ("
    " "{") 581 | ("
    " "{") 582 | ("
    " "{")) 583 | 584 | (jsx-deftest spread-no-value-no-dots-with-rc 585 | ("
    " "{}") 586 | ("
    " "{}") 587 | ("
    " "{}") 588 | ("
    " "{}") 589 | ("
    " "{}")) 590 | 591 | (jsx-deftest spread-no-value-with-dots-no-rc 592 | :expect-fail 593 | ("
    " "{...") 594 | ("
    " "{...") 595 | ("
    " "{...") 596 | ("
    " "{...") 597 | ("
    " "{...")) 598 | 599 | (jsx-deftest spread-no-value-with-dots-with-rc 600 | ("
    " "{...}") 601 | ("
    " "{...}") 602 | ("
    " "{...}") 603 | ("
    " "{...}") 604 | ("
    " "{...}")) 605 | 606 | (jsx-deftest spread-bad-value-no-dots-no-rc 607 | :expect-fail 608 | ("
    " "{&&") 609 | ("
    " "{&&") 610 | ("
    " "{&&") 611 | ("
    " "{&&") 612 | ("
    " "{&&")) 613 | 614 | (jsx-deftest spread-bad-value-no-dots-with-rc 615 | ("
    " "{&&}") 616 | ("
    " "{&&}") 617 | ("
    " "{&&}") 618 | ("
    " "{&&}") 619 | ("
    " "{&&}")) 620 | 621 | (jsx-deftest spread-bad-value-with-dots-with-rc 622 | ("
    " "{...&&}") 623 | ("
    " "{...&&}") 624 | ("
    " "{...&&}") 625 | ("
    " "{...&&}") 626 | ("
    " "{...&&}")) 627 | 628 | (jsx-deftest spread-good-value-no-dots-no-rc 629 | :expect-fail 630 | ("
    " "{{a: 123}") 631 | ("
    " "{{a: 123}") 632 | ("
    " "{{a: 123}") 633 | ("
    " "{{a: 123}") 634 | ("
    " "{{a: 123}")) 635 | 636 | (jsx-deftest spread-good-value-no-dots-with-rc 637 | ("
    " "{{a: 123}}") 638 | ("
    " "{{a: 123}}") 639 | ("
    " "{{a: 123}}") 640 | ("
    " "{{a: 123}}") 641 | ("
    " "{{a: 123}}")) 642 | 643 | 644 | 645 | ;; Other odds and ends 646 | 647 | (ert-deftest rjsx-<->-token-class () 648 | (ert-with-test-buffer (:name 'origin) 649 | (dolist (test '(("<" "div/" ">") 650 | ("<" "div" ">" "" "<" "/div" ">") 651 | ("<" "div" ">" "" "<" "/vid" ">") 652 | ("<" "" ">" "" "<" "/" ">") 653 | ("<" "div" ">" "\n hi\n {123 < 5}\n " "<" "span/" ">" "\n" "<" "/div" ">"))) 654 | (erase-buffer) 655 | (mapc #'insert test) 656 | (rjsx-mode) 657 | (js2-reparse) 658 | (goto-char (point-min)) 659 | (let ((is-tag t)) 660 | (dolist (frag test) 661 | (cond 662 | ((string= frag "")) 663 | (is-tag (should (memq (get-char-property (point) 'rjsx-class) '(< >)))) 664 | (t (should-not (memq (get-char-property (point) 'rjsx-class) '(< >))))) 665 | (setq is-tag (not is-tag)) 666 | (forward-char (length frag))))))) 667 | 668 | (ert-deftest rjsx-node-opening-tag () 669 | (ert-with-test-buffer (:name 'origin) 670 | (dolist (test '(("
    " "div" "div") 671 | ("
    " "div" "div") 672 | ("
    " "div" "div") 673 | ("" "C-d-e:f-g-h" "C-d-e:f-g-h") 674 | ("" "C.D.E" "C.D.E") 675 | ("" "C-a.D-a.E-a" nil) 676 | ("<>" "" ""))) 677 | (erase-buffer) 678 | (js2-visit-ast 679 | (js2-test-string-to-ast (car test)) 680 | (lambda (node end-p) 681 | (when (not end-p) 682 | (cond 683 | ((rjsx-node-p node) 684 | (should (string= (cadr test) (rjsx-node-opening-tag-name node)))) 685 | ((rjsx-closing-tag-p node) 686 | (should (string= (caddr test) (rjsx-closing-tag-full-name node)))))) 687 | nil))))) 688 | 689 | (ert-deftest rjsx-electric-lt () 690 | (let ((cases '("let c = " 691 | "let c = (\n " 692 | "let c = (\n
    \n " 693 | "let c = name => " 694 | "let c = (\n
    \n {value}\n " 695 | "let c =
    ,\n " 697 | "let c =
    {a && " 698 | "let c =
    {a || " 699 | "let c =
    {a ? " 700 | "let c =
    {a ? null :" 701 | "return " 702 | "return /*\n hello world \n*/\n // more comments\n "))) 703 | (ert-with-test-buffer (:name 'origin) 704 | (dolist (contents cases) 705 | (insert contents) 706 | (rjsx-mode) ; rjsx-electric-lt depends on the syntax table 707 | (rjsx-electric-lt 1) 708 | (should (string= (buffer-substring-no-properties (point-min) (point)) 709 | (concat contents "<"))) 710 | (should (string= (buffer-substring-no-properties (point) (point-max)) 711 | "/>")) 712 | (erase-buffer))))) 713 | 714 | (ert-deftest rjsx-electric-lt-in-jsx-text () 715 | "Regression test for #68" 716 | ;; This test ensures that newlines are properly skipped in JSX text when looking for the prior 717 | ;; token. In JSX text, newlines are not whitespace (cf #67) or comment enders, so aren't skipped 718 | ;; over by forward-comment 719 | (ert-with-test-buffer (:name 'electric-lt-in-jsx-text) 720 | (let ((pre "let c = (\n
    \n ") 721 | (post "\n
    \n);")) 722 | (insert pre) 723 | (save-excursion (insert post)) 724 | (rjsx-mode) 725 | (js2-reparse) 726 | (rjsx-electric-lt 1) 727 | (should (string= (buffer-substring-no-properties (point-min) (point)) 728 | (concat pre "<"))) 729 | (should (string= (buffer-substring-no-properties (point) (point-max)) 730 | (concat "/>" post))) 731 | (erase-buffer)))) 732 | 733 | (ert-deftest rjsx-electric-lt-grounded () 734 | (let ((cases '("let c = 3 " 735 | "if (n " 736 | "(abc && def) "))) 737 | (ert-with-test-buffer (:name 'origin) 738 | (dolist (contents cases) 739 | (insert contents) 740 | (rjsx-electric-lt 1) 741 | (should (string= (buffer-substring-no-properties (point-min) (point)) 742 | (concat contents "<"))) 743 | (should (string= (buffer-substring-no-properties (point) (point-max)) 744 | "")) 745 | (erase-buffer))))) 746 | 747 | (ert-deftest rjsx-electric-lt-prefix-arg () 748 | (let ((cases '("let c = " 749 | "let c = (\n " 750 | "let c = (\n
    \n " 751 | "let c = name => " 752 | "let c = (\n
    \n {value}\n " 753 | "let c =
    ,\n " 755 | "let c =
    {a && " 756 | "let c =
    {a || " 757 | "let c =
    {a ? " 758 | "let c =
    {a ? null :" 759 | "return " 760 | "return /*\n hello world \n*/\n // more comments\n "))) 761 | (ert-with-test-buffer (:name 'origin) 762 | (dolist (contents cases) 763 | (insert contents) 764 | (rjsx-mode) 765 | (rjsx-electric-lt 3) 766 | (should (string= (buffer-substring-no-properties (point-min) (point)) 767 | (concat contents "<<<"))) 768 | (should (string= (buffer-substring-no-properties (point) (point-max)) 769 | "")) 770 | (erase-buffer))))) 771 | 772 | (ert-deftest rjsx-electric-gt () 773 | (let ((cases '("let c = (\n
    \n \n
    )" 774 | "let c = " 775 | "let c = (\n \n)" 776 | "let c = name => " 777 | "let c = (\n
    \n {value}\n \n
    )" 778 | "let c =
    }/>" 779 | "let c =
    {a && }
    " 780 | "let c =
    {a || }
    " 781 | "let c =
    {a ? : null}
    " 782 | "let c =
    {a ? null : }
    " 783 | "return "))) 784 | (ert-with-test-buffer (:name 'origin) 785 | (dolist (contents cases) 786 | (insert contents) 787 | (goto-char 0) 788 | (search-forward "/>") 789 | (backward-char 2) 790 | (js2-mode--and-parse) 791 | (let ((start-point (point))) 792 | (rjsx-electric-gt 1) 793 | (should (= (1+ start-point) (point))) 794 | (should (string= (buffer-substring-no-properties (point-min) (point)) 795 | (concat (substring contents 0 (1- start-point)) ">"))) 796 | (should (string= (buffer-substring-no-properties (point) (point-max)) 797 | (concat "" (substring contents (1+ start-point))))) 798 | (erase-buffer)) 799 | (message "succeeded with %s" (prin1 contents)))))) 800 | 801 | (ert-deftest rjsx-electric-gt-grounded () 802 | (let ((cases '(("let C = () => (\n /}>\n);") 803 | ("let c = 3 " . "+ 4") 804 | ("if (n " . "=== undefined) return;") 805 | ("(abc && def) " . "\n") 806 | ("<". "/Component")))) 807 | (ert-with-test-buffer (:name 'origin) 808 | (dolist (contents cases) 809 | (insert (car contents)) 810 | (save-excursion (insert (cdr contents))) 811 | (js2-mode--and-parse) 812 | (rjsx-electric-gt 1) 813 | (should (string= (buffer-substring-no-properties (point-min) (point)) 814 | (concat (car contents) ">"))) 815 | (should (string= (buffer-substring-no-properties (point) (point-max)) 816 | (cdr contents))) 817 | (erase-buffer))))) 818 | 819 | (ert-deftest rjsx-electric-gt-prefix-arg () 820 | (let ((cases '("let c = (\n
    \n \n
    )" 821 | "let c = " 822 | "let c = (\n \n)" 823 | "let c = name => " 824 | "let c = (\n
    \n {value}\n \n
    )" 825 | "let c =
    }/>" 826 | "let c =
    {a && }
    " 827 | "let c =
    {a || }
    " 828 | "let c =
    {a ? : null}
    " 829 | "let c =
    {a ? null : }
    " 830 | "return "))) 831 | (ert-with-test-buffer (:name 'origin) 832 | (dolist (contents cases) 833 | (insert contents) 834 | (goto-char 0) 835 | (search-forward "/>") 836 | (backward-char 2) 837 | (js2-mode--and-parse) 838 | (let ((start-point (point))) 839 | (rjsx-electric-gt 2) 840 | (should (= (+ 2 start-point) (point))) 841 | (should (string= (buffer-substring-no-properties (point-min) (point)) 842 | (concat (substring contents 0 (1- start-point)) ">>"))) 843 | (should (string= (buffer-substring-no-properties (point) (point-max)) 844 | (substring contents (1- start-point)))) 845 | (erase-buffer)) 846 | (message "succeeded with %s" (prin1 contents)))))) 847 | 848 | (ert-deftest rjsx-delete-creates-full-tag () 849 | (let ((cases '("let c = (\n
    \n \n
    )" 850 | "let c = " 851 | "let c = (\n \n)" 852 | "let c = name => " 853 | "let c = (\n
    \n {value}\n \n
    )" 854 | "let c =
    }/>" 855 | "let c =
    {a && }
    " 856 | "let c =
    {a || }
    " 857 | "let c =
    {a ? : null}
    " 858 | "let c =
    {a ? null : }
    " 859 | "return "))) 860 | (ert-with-test-buffer (:name 'origin) 861 | (dolist (contents cases) 862 | (insert contents) 863 | (goto-char 0) 864 | (search-forward "/>") 865 | (backward-char 2) 866 | (js2-mode--and-parse) 867 | (let ((start-point (point))) 868 | (rjsx-delete-creates-full-tag 1) 869 | (should (= (1+ start-point) (point))) 870 | (should (string= (buffer-substring-no-properties (point-min) (point)) 871 | (concat (substring contents 0 (1- start-point)) ">"))) 872 | (should (string= (buffer-substring-no-properties (point) (point-max)) 873 | (concat "" (substring contents (1+ start-point))))) 874 | (erase-buffer)) 875 | (message "succeeded with %s" (prin1 contents)))))) 876 | 877 | (ert-deftest rjsx-delete-normal () 878 | (let ((cases '(("let C = () => (\n /}>\n);") 879 | ("let c = 3 " . "+ 4") 880 | ("if (n " . "=== undefined) return;") 881 | ("(abc && def) " . "\n") 882 | ("<". "/Component")))) 883 | (ert-with-test-buffer (:name 'origin) 884 | (dolist (contents cases) 885 | (insert (car contents)) 886 | (save-excursion (insert (cdr contents))) 887 | (js2-mode--and-parse) 888 | (rjsx-delete-creates-full-tag 1) 889 | (should (string= (buffer-substring-no-properties (point-min) (point)) 890 | (car contents))) 891 | (should (string= (buffer-substring-no-properties (point) (point-max)) 892 | (substring (cdr contents) 1))) 893 | (erase-buffer))))) 894 | 895 | (ert-deftest rjsx-delete-normal-region () 896 | (let ((cases '(("let C = () => (\n /}>\n);") 897 | ("let c = 3 " . "+ 4") 898 | ("if (n " . "=== undefined) return;") 899 | ("<". "/Component")))) 900 | (ert-with-test-buffer (:name 'origin) 901 | (dolist (contents cases) 902 | (insert (car contents)) 903 | (save-excursion (insert (cdr contents))) 904 | (js2-mode--and-parse) 905 | (let ((transient-mark-mode 1) 906 | (delete-active-region t)) 907 | (set-mark (point)) 908 | (forward-char 3) 909 | (call-interactively 'rjsx-delete-creates-full-tag)) 910 | (should (string= (buffer-substring-no-properties (point-min) (point)) 911 | (car contents))) 912 | (should (string= (buffer-substring-no-properties (point) (point-max)) 913 | (substring (cdr contents) 3))) 914 | (erase-buffer))))) 915 | 916 | (ert-deftest rjsx--tag-at-point () 917 | (let ((cases '(("let c = (\n " "
    \n \n
    )" nil) 918 | ("let c = (\n " "
    \n \n
    )" "div") 919 | ("let c = (\n <" "div>\n \n
    )" "div") 920 | ("let c = (\n \n \n
    )" "div") 921 | ("let c = (\n \n \n
    )" "div") 922 | ("let c = (\n
    \n " " \n
    )" "div") 923 | ("let c = (\n
    \n " "\n
    )" "Component") 924 | ("let c = (\n
    \n <" "Component a=\"123\"/>\n
    )" "Component") 925 | ("let c = (\n
    \n \n
    )" "Component") 926 | ("let c = (\n
    \n \n
    )" "Component") 927 | ("let c = (\n
    \n \n
    )" "Component") 928 | ("let c = (\n
    \n \n
    )" "Component") 929 | ("let c = (\n
    \n \n
    )" "Component") 930 | ("let c = (\n
    \n \n
    )" "Component") 931 | ("let c = (\n
    \n \n
    )" "Component") 932 | ("let c = (\n
    \n \n
    )" "Component") 933 | ("let c = (\n
    \n " "\n
    )" "div") 934 | ("let c = (\n " " \n)" nil) 935 | ("let c = (\n \n)" "Component") 936 | ("let c = (\n \n)" "Component") 937 | ("let c = (\n \n)" "Component") 938 | ("let c = (\n \n)" "Component") 939 | ("let c = (\n \n)" "Component") 940 | ("let c = (\n \n)" "Component") 941 | ("let c = (\n \n)" "Component") 942 | ("let c = (\n \n)" "Component") 943 | ("let c = (\n " "\n)" nil) 944 | ("let c = (\n
    \n " "{value}\n \n
    )" "div") 945 | ("let c = (\n
    \n {" "value}\n \n
    )" "div") 946 | ("let c = (\n
    \n {v" "alue}\n \n
    )" "div") 947 | ("let c = (\n
    \n {value" "}\n \n
    )" "div") 948 | ("let c = (\n
    \n {value}" "\n \n
    )" "div") 949 | ("let c =
    }/>" "div") 950 | ("let c =
    }/>" "Component") 951 | ("let c =
    }/>" "Component") 952 | ("let c =
    }/>" "Component") 953 | ("let c =
    }/>" "Component") 954 | ("let c =
    }/>" "Component") 955 | ("let c =
    " "}/>" "div") 956 | ("let c =
    }" "/>" "div") 957 | ("let c =
    }/" ">" "div") 958 | ("let c =
    }/>" "" nil) 959 | ("let c =
    {a && " "}
    " "Component") 960 | ("let c =
    {a && }
    " "Component") 961 | ("let c =
    {a &&" " }
    " "div") 962 | ("let c =
    {a && " "}
    " "div")))) 963 | (ert-with-test-buffer (:name 'origin) 964 | (dolist (case cases) 965 | (erase-buffer) 966 | (cl-destructuring-bind (pre post exp) case 967 | (insert pre) 968 | (save-excursion (insert post)) 969 | (js2-mode--and-parse) 970 | (let ((tag (rjsx--tag-at-point))) 971 | (cond 972 | (exp 973 | (should (not (eq nil tag))) 974 | (should (rjsx-node-p tag)) 975 | (should (string= (rjsx-node-opening-tag-name tag) exp))) 976 | (t 977 | (should (eq nil tag)))))))))) 978 | 979 | (ert-deftest rjsx-rename-tag-at-point () 980 | (let ((cases '(("let c = (\n " "
    \n \n
    )" 981 | "let c = (\n
    \n \n
    )") 982 | ("let c = (\n
    \n \n
    )" 983 | "let c = (\n
    \n \n
    )") 984 | ("let c = (\n \n)" 985 | "let c = (\n \n)") 986 | ("let c =
    }/>" 987 | "let c =
    }/>") 988 | ("let c =
    {a && }
    " 989 | "let c =
    {a && }
    ") 990 | ("let c =
    {a && <" ">}
    " 991 | "let c =
    {a && }
    ") 992 | ("let c =
    {a && <>x" "yz\n }
    " 993 | "let c =
    {a && xyz\n }
    ") 994 | ("let c =
    {a && <" ">}
    " 995 | "let c =
    {a && }
    ")))) 996 | (ert-with-test-buffer (:name 'origin) 997 | (dolist (case cases) 998 | (erase-buffer) 999 | (cl-destructuring-bind (pre post exp) case 1000 | (insert pre) 1001 | (save-excursion (insert post)) 1002 | (js2-mode--and-parse) 1003 | (rjsx-rename-tag-at-point "NewName") 1004 | (message (concat pre "|" post)) 1005 | (should (string= (buffer-substring-no-properties (point-min) (point-max)) exp))))))) 1006 | 1007 | (ert-deftest rjsx-auto-reparse () 1008 | (ert-with-test-buffer (:name 'rjsx) 1009 | (erase-buffer) 1010 | (rjsx-mode) 1011 | (insert "let c =
    {a && }
    ")) 1013 | (setq js2-mode-ast nil) 1014 | (rjsx--tag-at-point) ;; Should not error 1015 | (setq js2-mode-ast nil) 1016 | (let ((rjsx-max-size-for-frequent-reparse (1- (point-max)))) 1017 | (should-error (rjsx--tag-at-point))))) 1018 | 1019 | 1020 | ;; Indentation 1021 | (defun rjsx-tests--test-indent (pre &optional post test-buffer-name) 1022 | "Assert that calling `indent-region' on PRE results in POST. 1023 | TEST-BUFFER-NAME is used for `ert-with-test-buffer'." 1024 | (unless post (setq post pre)) 1025 | (ert-with-test-buffer (:name (or test-buffer-name 'rjsx-indentation-test)) 1026 | (insert pre) 1027 | (rjsx-mode) 1028 | (js2-reparse) 1029 | (indent-region (point-min) (point-max)) 1030 | (should (string= (buffer-substring-no-properties (point-min) (point-max)) post)))) 1031 | 1032 | (ert-deftest rjsx-indent-region () 1033 | (rjsx-tests--test-indent 1034 | "( 1035 |
    1036 | { 1037 | [1,2,3].map(num => { 1038 | return (
    ); 1039 | }) 1040 | } 1041 |
    1044 | 1 1045 | \"\" ( 1049 |
    whatever
    1050 | )} 1051 | /> 1052 |
    1053 |
    1054 | )" 1055 | "( 1056 |
    1057 | { 1058 | [1,2,3].map(num => { 1059 | return (
    ); 1060 | }) 1061 | } 1062 |
    1065 | 1 1066 | \"\" ( 1070 |
    whatever
    1071 | )} 1072 | /> 1073 |
    1074 |
    1075 | )")) 1076 | 1077 | (ert-deftest rjsx-indentation-1 () 1078 | "Regression test for #67." 1079 | (rjsx-tests--test-indent "function Example(props) { 1080 | return ( 1081 |
      1082 | { 1083 | [1,2,3].map(lang => { 1084 | return( 1085 |
    • 1086 | {lang} 1087 |
    • 1088 | ) 1089 | }) 1090 | } 1091 |
    1092 | ) 1093 | }" nil 'rjsx-indentation-1)) 1094 | 1095 | (ert-deftest rjsx-indentation-2 () 1096 | "Indentation sample from mooz/js2-mode#490." 1097 | (rjsx-tests--test-indent 1098 | "( 1099 | 1100 |
    1101 | {variable1} 1102 | 1103 |
    1104 |
    1105 | )" 1106 | "( 1107 | 1108 |
    1109 | {variable1} 1110 | 1111 |
    1112 |
    1113 | )")) 1114 | 1115 | (ert-deftest rjsx-indentation-3 () 1116 | "Indentation sample from mooz/js2-mode#462" 1117 | (rjsx-tests--test-indent 1118 | "function F() { 1119 | return ( 1120 | 1121 | 1122 | ( 1123 |
    nothing
    1124 | )} /> 1125 | 1126 |
    1127 |
    1128 | ) 1129 | }" 1130 | "function F() { 1131 | return ( 1132 | 1133 | 1134 | ( 1135 |
    nothing
    1136 | )} /> 1137 | 1138 |
    1139 |
    1140 | ) 1141 | }")) 1142 | 1143 | (ert-deftest rjsx-indentation-4 () 1144 | "Indentation sample from mooz/js2-mode#451." 1145 | (rjsx-tests--test-indent 1146 | "class Classy extends React.Component { 1147 | render () { 1148 | return ( 1149 |
    1150 |
      1151 | { this.state.list.map((item) => { 1152 | return (
      ) 1153 | })} 1154 |
    1155 |
    1156 | ) 1157 | } 1158 | }" 1159 | "class Classy extends React.Component { 1160 | render () { 1161 | return ( 1162 |
    1163 |
      1164 | { this.state.list.map((item) => { 1165 | return (
      ) 1166 | })} 1167 |
    1168 |
    1169 | ) 1170 | } 1171 | }")) 1172 | 1173 | (ert-deftest rjsx-indentation-5 () 1174 | "Trailing `>' with arrow function." 1175 | (rjsx-tests--test-indent 1176 | "( 1177 |
    1178 |
    123 1181 | )} 1182 | > 1183 |
    1184 |
    1185 | )" 1186 | "( 1187 |
    1188 |
    123 1191 | )} 1192 | > 1193 |
    1194 |
    1195 | )")) 1196 | 1197 | (ert-deftest rjsx-indentation-6 () 1198 | "Trailing `>' with nested JSX." 1199 | (rjsx-tests--test-indent 1200 | "( 1201 |
    1202 |
    whatever
    1205 | )} 1206 | > 1207 |
    1208 |
    1209 | )" "( 1210 |
    1211 |
    whatever
    1214 | )} 1215 | > 1216 |
    1217 |
    1218 | )")) 1219 | 1220 | (ert-deftest rjsx-indentation-7 () 1221 | "Attribute after nested JSX." 1222 | (rjsx-tests--test-indent 1223 | "( 1224 |
    1225 |
    whatever
    1228 | )} 1229 | id='123' 1230 | > 1231 |
    1232 |
    1233 | )" "( 1234 |
    1235 |
    whatever
    1238 | )} 1239 | id='123' 1240 | > 1241 |
    1242 |
    1243 | )")) 1244 | 1245 | (ert-deftest rjsx-indentation-8 () 1246 | "Inline JSX." 1247 | (rjsx-tests--test-indent 1248 | "const c =
    ;")) 1249 | 1250 | (ert-deftest rjsx-indentation-9 () 1251 | "Line break after opening '<'; attributes on their own lines." 1252 | (rjsx-tests--test-indent 1253 | "const c = <\n divxyz\n abc={123}\n def/>;")) 1254 | 1255 | (ert-deftest rjsx-indentation-10 () 1256 | "Line brak after opening '<'; 1st attribute in same line as name." 1257 | (rjsx-tests--test-indent 1258 | "const c = <\n divxyz abc={123}\n def/>;")) 1259 | 1260 | (ert-deftest rjsx-indentation-11 () 1261 | "Empty line in JSX." 1262 | (rjsx-tests--test-indent 1263 | "const c = ;" 1264 | "const c = ;")) 1265 | 1266 | (defun rjsx-tests--test-point-after-indent (text-before text-after post-offset) 1267 | "Test that point is correctly placed after indenting. 1268 | Insert TEXT-BEFORE and TEXT-AFTER into a buffer, leaving point 1269 | between them. Then run `rjsx-indent-line' and assert that it is 1270 | on the same line number and POST-OFFSET columns in." 1271 | (ert-with-test-buffer (:name 'rjsx-tests--test-point-after-indent) 1272 | (insert text-before) 1273 | (save-excursion (insert text-after)) 1274 | (rjsx-mode) 1275 | (js2-reparse) 1276 | (let ((line-no (line-number-at-pos))) 1277 | (rjsx-indent-line) 1278 | (should (= (line-number-at-pos) line-no)) 1279 | (should (= (- (current-column) (current-indentation)) post-offset))))) 1280 | 1281 | (ert-deftest rjsx-indentation-point-in-whitespace () 1282 | (rjsx-tests--test-point-after-indent 1283 | "const c = ;" 0) 1284 | (rjsx-tests--test-point-after-indent 1285 | "const c = ;" 0) 1286 | (rjsx-tests--test-point-after-indent 1287 | "const c = ;" 0)) 1288 | 1289 | (ert-deftest rjsx-indentation-point-in-body () 1290 | (rjsx-tests--test-point-after-indent 1291 | "const c = ;" 6) 1292 | (rjsx-tests--test-point-after-indent 1293 | "const c = ;" 6) 1294 | (rjsx-tests--test-point-after-indent 1295 | "const c = ;" 6)) 1296 | 1297 | (ert-deftest rjsx-indentation-point-at-eol () 1298 | (rjsx-tests--test-point-after-indent 1299 | "const c = ;" 9) 1300 | (rjsx-tests--test-point-after-indent 1301 | "const c = ;" 9) 1302 | (rjsx-tests--test-point-after-indent 1303 | "const c = ;" 9)) 1304 | 1305 | (ert-deftest rjsx-jump-closing-tag () 1306 | "Test that point is correctly placed on jumping to closing tag" 1307 | (ert-with-test-buffer (:name 'rjsx-jump-closing-tag) 1308 | (erase-buffer) 1309 | (insert "let c = \n
    ")) 1311 | (rjsx-mode) 1312 | (rjsx-jump-closing-tag) 1313 | (should (= (line-number-at-pos) 2)) 1314 | (should (= (current-column) 1)))) 1315 | 1316 | 1317 | (ert-deftest rjsx-jump-opening-tag () 1318 | "Test that point is correctly placed on jumping to opening tag" 1319 | (ert-with-test-buffer (:name 'rjsx-jump-opening-tag) 1320 | (erase-buffer) 1321 | (insert "let c =
    \n")) 1323 | (rjsx-mode) 1324 | (rjsx-jump-opening-tag) 1325 | (should (= (line-number-at-pos) 1)) 1326 | (should (= (current-column) 9)) 1327 | )) 1328 | 1329 | 1330 | (ert-deftest rjsx-jump-tag () 1331 | "Test that point is correctly placed on jumping to opening tag" 1332 | (ert-with-test-buffer (:name 'rjsx-jump-opening-tag) 1333 | (erase-buffer) 1334 | (insert "let c =
    \n")) 1336 | (rjsx-mode) 1337 | (rjsx-jump-tag) 1338 | (should (= (line-number-at-pos) 1)) 1339 | (should (= (current-column) 9)) 1340 | (rjsx-jump-tag) 1341 | (should (= (line-number-at-pos) 2)) 1342 | (should (= (current-column) 1)) 1343 | )) 1344 | 1345 | ;; Minor-mode 1346 | 1347 | (defun rjsx-test-minor-mode-string-to-ast (s) 1348 | "Convert S into a js2 AST node." 1349 | (insert s) 1350 | (rjsx-minor-mode 1) 1351 | (js2-reparse) 1352 | (should (null js2-mode-buffer-dirty-p)) 1353 | js2-mode-ast) 1354 | 1355 | (ert-deftest rjsx-minor-mode-parsing () 1356 | "Test that the parser works when the minor mode is active." 1357 | (let* ((s "
    1358 | this._topInput = c}/> 1363 | {errors.name && {errors.name}} 1364 | { } Empty is OK as child, but warning is issued 1365 | 1366 | {/* Node with comment gets no warning */} 1367 |
    Empty attributes are allowed 1368 | {React.Children.count(this.props.children) === 1 1369 | ? {this.props.children} 1370 | : React.Children.map(this.props.children, (child, index) => ( 1371 |
  • 1372 | {index === 0 && } 1373 | {React.cloneElement(child, {toolTip: })} 1374 |
  • 1375 | )) 1376 | } 1377 | ") 1378 | (ref "
    1379 | {this._topInput = c}}/> 1380 | {errors.name && {errors.name}} 1381 | {} Empty is OK as child, but warning is issued 1382 | 1383 | {} 1384 |
    Empty attributes are allowed 1385 | {React.Children.count(this.props.children) === 1 ? {this.props.children} : React.Children.map(this.props.children, (child, index) => {(
  • 1386 | {index === 0 && } 1387 | {React.cloneElement(child, {toolTip: })} 1388 |
  • )})} 1389 | ;") 1390 | (ast (rjsx-test-minor-mode-string-to-ast s)) 1391 | (printed 1392 | (ert-with-test-buffer (:name 'rjsx-minor-mode-printing) 1393 | (js2-print-tree ast) 1394 | (skip-chars-backward " \t\n") 1395 | (buffer-substring-no-properties (point-min) (point))))) 1396 | (should (string= ref printed)) 1397 | (should (= 0 (length (js2-ast-root-errors ast)))) 1398 | (should (equal (assoc '("msg.empty.expr" nil) 1399 | (js2-ast-root-warnings ast)) 1400 | '(("msg.empty.expr" nil) 327 5 nil))))) 1401 | 1402 | 1403 | 1404 | ;; Comments 1405 | (cl-defmacro rjsx-deftest-comment (name fixture expected &optional (command nil) &rest pre-command) 1406 | "Macro to define comment tests. 1407 | 1408 | Defines a comment ERT test named NAME. The test is setup by first 1409 | inserting the FIXTURE string into a ERT test buffer and invoking 1410 | `rjsx-mode'. It then executes PRE-COMMAND, then optionally 1411 | COMMAND, then asserts the buffer content is the same as EXPECTED, 1412 | then finaly cleans up the buffer and exits. 1413 | 1414 | COMMAND is not needed if the comment command under test is 1415 | invoked explicitly in PRE-COMMAND. 1416 | " 1417 | (declare (indent defun)) 1418 | `(ert-deftest ,(intern (format "rjsx-%s" name)) () 1419 | (ert-with-test-buffer (:name ',name) 1420 | (let ((fixture ,fixture) 1421 | (expected ,expected)) 1422 | (erase-buffer) 1423 | (insert fixture) 1424 | (goto-char (point-min)) 1425 | (rjsx-mode) 1426 | (js2-reparse) 1427 | 1428 | ,@pre-command 1429 | 1430 | (when ,command 1431 | (call-interactively ,command)) 1432 | 1433 | (should (string= (buffer-substring-no-properties (point-min) (point-max)) expected)) 1434 | 1435 | (erase-buffer))))) 1436 | 1437 | (rjsx-deftest-comment 1438 | comment-dwim-js-line 1439 | "for (let i = 0; i < 10; i++) { 1440 | console.log(i); 1441 | } 1442 | " 1443 | "for (let i = 0; i < 10; i++) { // 1444 | console.log(i); 1445 | } 1446 | " 1447 | 'rjsx-comment-dwim) 1448 | 1449 | (rjsx-deftest-comment 1450 | comment-dwim-js-region 1451 | "for (let i = 0; i < 10; i++) { 1452 | console.log(i); 1453 | } 1454 | " 1455 | "// for (let i = 0; i < 10; i++) { 1456 | // console.log(i); 1457 | // } 1458 | " 1459 | 'rjsx-comment-dwim 1460 | (transient-mark-mode 1) 1461 | (set-mark (point)) 1462 | (goto-char (point-max))) 1463 | 1464 | (rjsx-deftest-comment 1465 | comment-dwim-region-top-level-element 1466 | "( 1467 |
    1468 |
    1469 | )" 1470 | "( 1471 | //
    1472 | //
    1473 | )" 1474 | 'rjsx-comment-dwim 1475 | (forward-line 1) 1476 | (transient-mark-mode 1) 1477 | (set-mark (point)) 1478 | (forward-line 2)) 1479 | 1480 | (rjsx-deftest-comment 1481 | uncomment-dwim-region-top-level-element 1482 | "( 1483 | //
    1484 | //
    1485 | )" 1486 | "( 1487 |
    1488 |
    1489 | )" 1490 | 'rjsx-comment-dwim 1491 | (forward-line 1) 1492 | (transient-mark-mode 1) 1493 | (set-mark (point)) 1494 | (forward-line 2)) 1495 | 1496 | (rjsx-deftest-comment 1497 | comment-dwim-region-child-element 1498 | "( 1499 |
    1500 | 1 1501 | 1 1502 |
    1503 | )" 1504 | "( 1505 |
    1506 | {/* 1 */} 1507 | {/* 1 */} 1508 |
    1509 | )" 1510 | 'rjsx-comment-dwim 1511 | (forward-line 2) 1512 | (transient-mark-mode 1) 1513 | (set-mark (point)) 1514 | (forward-line 2)) 1515 | 1516 | (rjsx-deftest-comment 1517 | uncomment-dwim-region-child-element 1518 | "( 1519 |
    1520 | {/* 1 */} 1521 | {/* 1 */} 1522 |
    1523 | )" 1524 | "( 1525 |
    1526 | 1 1527 | 1 1528 |
    1529 | )" 1530 | 'rjsx-comment-dwim 1531 | (forward-line 2) 1532 | (transient-mark-mode 1) 1533 | (set-mark (point)) 1534 | (forward-line 2)) 1535 | 1536 | (rjsx-deftest-comment 1537 | comment-dwim-region-attr 1538 | "( 1539 |
    1543 | 1 1544 |
    1545 | )" 1546 | "( 1547 |
    1551 | 1 1552 |
    1553 | )" 1554 | 'rjsx-comment-dwim 1555 | (forward-line 2) 1556 | (transient-mark-mode 1) 1557 | (set-mark (point)) 1558 | (forward-line 2)) 1559 | 1560 | (rjsx-deftest-comment 1561 | uncomment-dwim-region-attr 1562 | "( 1563 |
    1567 | 1 1568 |
    1569 | )" 1570 | "( 1571 |
    1575 | 1 1576 |
    1577 | )" 1578 | 'rjsx-comment-dwim 1579 | (forward-line 2) 1580 | (transient-mark-mode 1) 1581 | (set-mark (point)) 1582 | (forward-line 2)) 1583 | 1584 | (rjsx-deftest-comment 1585 | comment-dwim-region-text 1586 | "( 1587 |
    1588 | hello 1589 | world 1590 |
    1591 | )" 1592 | "( 1593 |
    1594 | {/* hello */} 1595 | {/* world */} 1596 |
    1597 | )" 1598 | 'rjsx-comment-dwim 1599 | (forward-line 2) 1600 | (transient-mark-mode 1) 1601 | (set-mark (point)) 1602 | (forward-line 2)) 1603 | 1604 | (rjsx-deftest-comment 1605 | uncomment-dwim-region-text 1606 | "( 1607 |
    1608 | {/* hello */} 1609 | {/* world */} 1610 |
    1611 | )" 1612 | "( 1613 |
    1614 | hello 1615 | world 1616 |
    1617 | )" 1618 | 'rjsx-comment-dwim 1619 | (forward-line 2) 1620 | (transient-mark-mode 1) 1621 | (set-mark (point)) 1622 | (forward-line 2)) 1623 | 1624 | (rjsx-deftest-comment 1625 | comment-dwim-region-wrapped-expr-outer 1626 | "( 1627 |
    1628 | {[1,2,3].map(num => ( 1629 |
    1630 | {num} 1631 |
    1632 | ))} 1633 |
    1634 | )" 1635 | "( 1636 |
    1637 | {/* {[1,2,3].map(num => ( */} 1638 | {/*
    */} 1639 | {/* {num} */} 1640 | {/*
    */} 1641 | {/* ))} */} 1642 |
    1643 | )" 1644 | 'rjsx-comment-dwim 1645 | (forward-line 2) 1646 | (transient-mark-mode 1) 1647 | (set-mark (point)) 1648 | (forward-line 5)) 1649 | 1650 | (rjsx-deftest-comment 1651 | uncomment-dwim-region-wrapped-expr-outer 1652 | "( 1653 |
    1654 | {/* {[1,2,3].map(num => ( */} 1655 | {/*
    */} 1656 | {/* {num} */} 1657 | {/*
    */} 1658 | {/* ))} */} 1659 |
    1660 | )" 1661 | "( 1662 |
    1663 | {[1,2,3].map(num => ( 1664 |
    1665 | {num} 1666 |
    1667 | ))} 1668 |
    1669 | )" 1670 | 'rjsx-comment-dwim 1671 | (forward-line 2) 1672 | (transient-mark-mode 1) 1673 | (set-mark (point)) 1674 | (forward-line 5)) 1675 | 1676 | (rjsx-deftest-comment 1677 | comment-dwim-region-wrapped-expr-inner 1678 | "( 1679 |
    1680 | {[1,2,3].map(num => ( 1681 |
    1682 | {num} 1683 |
    1684 | ))} 1685 |
    1686 | )" 1687 | "( 1688 |
    1689 | {[1,2,3].map(num => ( 1690 | /*
    */ 1691 | /* {num} */ 1692 | /*
    */ 1693 | ))} 1694 |
    1695 | )" 1696 | 'rjsx-comment-dwim 1697 | (forward-line 3) 1698 | (transient-mark-mode 1) 1699 | (set-mark (point)) 1700 | (forward-line 3)) 1701 | 1702 | (rjsx-deftest-comment 1703 | uncomment-dwim-region-wrapped-expr-inner 1704 | "( 1705 |
    1706 | {[1,2,3].map(num => ( 1707 | /*
    */ 1708 | /* {num} */ 1709 | /*
    */ 1710 | ))} 1711 |
    1712 | )" 1713 | "( 1714 |
    1715 | {[1,2,3].map(num => ( 1716 |
    1717 | {num} 1718 |
    1719 | ))} 1720 |
    1721 | )" 1722 | 'rjsx-comment-dwim 1723 | (forward-line 3) 1724 | (transient-mark-mode 1) 1725 | (set-mark (point)) 1726 | (forward-line 3)) 1727 | 1728 | (rjsx-deftest-comment 1729 | comment-dwim-top-level-element-single-value 1730 | "const Div = () =>
    ;" 1731 | "const Div = () => /*
    */;" 1732 | 'rjsx-comment-dwim 1733 | (search-forward "<")) 1734 | 1735 | (rjsx-deftest-comment 1736 | uncomment-dwim-top-level-element-single-value 1737 | "const Div = () => /*
    */;" 1738 | "const Div = () =>
    ;" 1739 | 'rjsx-comment-dwim 1740 | (search-forward "/")) 1741 | 1742 | (rjsx-deftest-comment 1743 | comment-dwim-top-level-element-single-value-parened 1744 | "const Div = () => (
    );" 1745 | "const Div = () => (/*
    */);" 1746 | 'rjsx-comment-dwim 1747 | (search-forward "<")) 1748 | 1749 | (rjsx-deftest-comment 1750 | uncomment-dwim-top-level-element-single-value-parened 1751 | "const Div = () => (/*
    */);" 1752 | "const Div = () => (
    );" 1753 | 'rjsx-comment-dwim 1754 | (search-forward "/")) 1755 | 1756 | (rjsx-deftest-comment 1757 | comment-dwim-top-level-element-vertical 1758 | "const Div = () => ( 1759 |
    1760 | hello 1761 | world 1762 |
    1763 | );" 1764 | "const Div = () => ( 1765 | /*
    */ 1766 | /* hello */ 1767 | /* world */ 1768 | /*
    */ 1769 | );" 1770 | 'rjsx-comment-dwim 1771 | (search-forward "<")) 1772 | 1773 | (rjsx-deftest-comment 1774 | uncomment-dwim-top-level-element-vertical 1775 | "const Div = () => ( 1776 | /*
    */ 1777 | /* hello */ 1778 | /* world */ 1779 | /*
    */ 1780 | );" 1781 | "const Div = () => ( 1782 |
    1783 | hello 1784 | world 1785 |
    1786 | );" 1787 | 'rjsx-comment-dwim 1788 | (forward-line 1) 1789 | (transient-mark-mode 1) 1790 | (set-mark (point)) 1791 | (forward-line 4)) 1792 | 1793 | (rjsx-deftest-comment 1794 | comment-dwim-nested-element 1795 | "(
    hello
    )" 1796 | "(
    {/* hello */}
    )" 1797 | 'rjsx-comment-dwim 1798 | (search-forward "{/* hello */}
    )" 1803 | "(
    hello
    )" 1804 | 'rjsx-comment-dwim 1805 | (search-forward "{")) 1806 | 1807 | (rjsx-deftest-comment 1808 | comment-dwim-attr-horizontal-className 1809 | "(
    )" 1810 | "(
    )" 1811 | nil 1812 | (search-forward "className") 1813 | (backward-char 1) 1814 | (js2-reparse) 1815 | (call-interactively 'rjsx-comment-dwim)) 1816 | 1817 | (rjsx-deftest-comment 1818 | uncomment-dwim-attr-horizontal-namespace 1819 | "(
    )" 1820 | "(
    )" 1821 | nil 1822 | (search-forward ":") 1823 | (js2-reparse) 1824 | (call-interactively 'rjsx-comment-dwim)) 1825 | 1826 | (rjsx-deftest-comment 1827 | uncomment-dwim-attr-horizontal-string 1828 | "(
    )" 1829 | "(
    )" 1830 | nil 1831 | (search-forward "bar=") 1832 | (js2-reparse) 1833 | (call-interactively 'rjsx-comment-dwim)) 1834 | 1835 | (rjsx-deftest-comment 1836 | uncomment-dwim-attr-horizontal-boolean 1837 | "(
    )" 1838 | "(
    )" 1839 | nil 1840 | (search-forward "baz") 1841 | (js2-reparse) 1842 | (call-interactively 'rjsx-comment-dwim)) 1843 | 1844 | (rjsx-deftest-comment 1845 | uncomment-dwim-attr-horizontal-spread 1846 | "(
    )" 1847 | "(
    )" 1848 | nil 1849 | (search-forward "...") 1850 | (js2-reparse) 1851 | (call-interactively 'rjsx-comment-dwim)) 1852 | 1853 | (rjsx-deftest-comment 1854 | comment-dwim-attr-vertical-className 1855 | "(
    ) 1858 | " 1859 | "(
    ) 1862 | " 1863 | nil 1864 | (forward-line 1) 1865 | (js2-reparse) 1866 | (call-interactively 'rjsx-comment-dwim)) 1867 | 1868 | (rjsx-deftest-comment 1869 | comment-dwim-attr-vertical-namespace 1870 | "(
    ) 1873 | " 1874 | "(
    ) 1877 | " 1878 | nil 1879 | (forward-line 1) 1880 | (js2-reparse) 1881 | (call-interactively 'rjsx-comment-dwim)) 1882 | 1883 | (rjsx-deftest-comment 1884 | comment-dwim-attr-vertical-string 1885 | "(
    ) 1888 | " 1889 | "(
    ) 1892 | " 1893 | nil 1894 | (forward-line 1) 1895 | (js2-reparse) 1896 | (call-interactively 'rjsx-comment-dwim)) 1897 | 1898 | (rjsx-deftest-comment 1899 | comment-dwim-attr-vertical-boolean 1900 | "(
    ) 1903 | " 1904 | "(
    ) 1907 | " 1908 | nil 1909 | (forward-line 1) 1910 | (js2-reparse) 1911 | (call-interactively 'rjsx-comment-dwim)) 1912 | 1913 | (rjsx-deftest-comment 1914 | comment-dwim-attr-vertical-spread 1915 | "(
    ) 1918 | " 1919 | "(
    ) 1922 | " 1923 | nil 1924 | (forward-line 1) 1925 | (js2-reparse) 1926 | (call-interactively 'rjsx-comment-dwim)) 1927 | 1928 | (rjsx-deftest-comment 1929 | uncomment-dwim-attr-vertical-className 1930 | "(
    ) 1933 | " 1934 | "(
    ) 1937 | " 1938 | nil 1939 | (forward-line 1) 1940 | (js2-reparse) 1941 | (call-interactively 'rjsx-comment-dwim)) 1942 | 1943 | (rjsx-deftest-comment 1944 | uncomment-dwim-attr-vertical-namespace 1945 | "(
    ) 1948 | " 1949 | "(
    ) 1952 | " 1953 | nil 1954 | (forward-line 1) 1955 | (js2-reparse) 1956 | (call-interactively 'rjsx-comment-dwim)) 1957 | 1958 | (rjsx-deftest-comment 1959 | uncomment-dwim-attr-vertical-string 1960 | "(
    ) 1963 | " 1964 | "(
    ) 1967 | " 1968 | nil 1969 | (forward-line 1) 1970 | (js2-reparse) 1971 | (call-interactively 'rjsx-comment-dwim)) 1972 | 1973 | (rjsx-deftest-comment 1974 | uncomment-dwim-attr-vertical-boolean 1975 | "(
    ) 1978 | " 1979 | "(
    ) 1982 | " 1983 | nil 1984 | (forward-line 1) 1985 | (js2-reparse) 1986 | (call-interactively 'rjsx-comment-dwim)) 1987 | 1988 | (rjsx-deftest-comment 1989 | uncomment-dwim-attr-vertical-spread 1990 | "(
    ) 1993 | " 1994 | "(
    ) 1997 | " 1998 | nil 1999 | (forward-line 1) 2000 | (js2-reparse) 2001 | (call-interactively 'rjsx-comment-dwim)) 2002 | 2003 | (rjsx-deftest-comment 2004 | comment-dwim-wrapped-expr-horizontal-inner 2005 | "(
    {[1,2,3].map(String)}{foo}
    )" 2006 | "(
    {/* [1,2,3].map(String) */}{/* foo */}
    )" 2007 | nil 2008 | (search-forward "{") 2009 | (js2-reparse) 2010 | (call-interactively 'rjsx-comment-dwim) 2011 | 2012 | (search-forward "{") 2013 | (js2-reparse) 2014 | (call-interactively 'rjsx-comment-dwim) 2015 | 2016 | (search-forward "{") 2017 | (js2-reparse) 2018 | (call-interactively 'rjsx-comment-dwim)) 2019 | 2020 | (rjsx-deftest-comment 2021 | comment-dwim-wrapped-expr-horizontal-outer 2022 | "(
    {[1,2,3].map(String)}{foo}
    )" 2023 | "(
    {/* [1,2,3].map(String) */}{/* foo */}
    )" 2024 | nil 2025 | (search-forward "{") 2026 | (backward-char 1) 2027 | (call-interactively 'rjsx-comment-dwim) 2028 | 2029 | (search-forward "{" (point-max) t 2) 2030 | (backward-char 1) 2031 | (js2-reparse) 2032 | (call-interactively 'rjsx-comment-dwim) 2033 | 2034 | (search-forward "{" (point-max) t 2) 2035 | (backward-char 1) 2036 | (js2-reparse) 2037 | (call-interactively 'rjsx-comment-dwim)) 2038 | 2039 | (rjsx-deftest-comment 2040 | uncomment-dwim-wrapped-expr-horizontal-inner 2041 | "(
    {/* [1,2,3].map(String) */}{/* foo */}
    )" 2042 | "(
    {[1,2,3].map(String)}{foo}
    )" 2043 | nil 2044 | (search-forward "{") 2045 | (js2-reparse) 2046 | (call-interactively 'rjsx-comment-dwim) 2047 | 2048 | (search-forward "{") 2049 | (js2-reparse) 2050 | (call-interactively 'rjsx-comment-dwim) 2051 | 2052 | (search-forward "{") 2053 | (js2-reparse) 2054 | (call-interactively 'rjsx-comment-dwim)) 2055 | 2056 | (rjsx-deftest-comment 2057 | uncomment-dwim-wrapped-expr-horizontal-outer 2058 | "(
    {/* [1,2,3].map(String) */}{/* foo */}
    )" 2059 | "(
    {[1,2,3].map(String)}{foo}
    )" 2060 | nil 2061 | (search-forward "{") 2062 | (backward-char 1) 2063 | (call-interactively 'rjsx-comment-dwim) 2064 | 2065 | (search-forward "{" (point-max) t 2) 2066 | (backward-char 1) 2067 | (js2-reparse) 2068 | (call-interactively 'rjsx-comment-dwim) 2069 | 2070 | (search-forward "{" (point-max) t 2) 2071 | (backward-char 1) 2072 | (js2-reparse) 2073 | (call-interactively 'rjsx-comment-dwim)) 2074 | 2075 | (rjsx-deftest-comment 2076 | comment-dwim-wrapped-expr-vertical-inner 2077 | "( 2078 |
    2081 | {} 2082 | {[1,2,3].map(String)} 2083 | { 2084 | [1,2,3].map(num => ( 2085 | num * num 2086 | )) 2087 | } 2088 | {foo} 2089 |
    2090 | )" 2091 | "( 2092 |
    2095 | {/* {} */} 2096 | {/* [1,2,3].map(String) */} 2097 | { // 2098 | [1,2,3].map(num => ( 2099 | num * num 2100 | )) 2101 | } 2102 | {/* foo */} 2103 |
    2104 | )" 2105 | nil 2106 | (search-forward "{") 2107 | (js2-reparse) 2108 | (call-interactively 'rjsx-comment-dwim) 2109 | 2110 | (search-forward "{") 2111 | (js2-reparse) 2112 | (call-interactively 'rjsx-comment-dwim) 2113 | 2114 | (search-forward "{") 2115 | (js2-reparse) 2116 | (call-interactively 'rjsx-comment-dwim) 2117 | 2118 | (search-forward "{") 2119 | (js2-reparse) 2120 | (call-interactively 'rjsx-comment-dwim) 2121 | 2122 | (search-forward "{") 2123 | (js2-reparse) 2124 | (call-interactively 'rjsx-comment-dwim)) 2125 | 2126 | (rjsx-deftest-comment 2127 | comment-dwim-wrapped-expr-vertical-outer 2128 | "( 2129 |
    2132 | {} 2133 | {[1,2,3].map(String)} 2134 | { 2135 | [1,2,3].map(num => ( 2136 | num * num 2137 | )) 2138 | } 2139 | {foo} 2140 |
    2141 | )" 2142 | "( 2143 |
    2146 | {/* {} */} 2147 | {/* [1,2,3].map(String) */} 2148 | { // 2149 | [1,2,3].map(num => ( 2150 | num * num 2151 | )) 2152 | } 2153 | {/* foo */} 2154 |
    2155 | )" 2156 | nil 2157 | (search-forward "{") 2158 | (backward-char 1) 2159 | (js2-reparse) 2160 | (call-interactively 'rjsx-comment-dwim) 2161 | 2162 | (search-forward "{" (point-max) t 2) 2163 | (backward-char 1) 2164 | (js2-reparse) 2165 | (call-interactively 'rjsx-comment-dwim) 2166 | 2167 | (search-forward "{" (point-max) t 3) 2168 | (backward-char 1) 2169 | (js2-reparse) 2170 | (call-interactively 'rjsx-comment-dwim) 2171 | 2172 | (search-forward "{" (point-max) t 2) 2173 | (backward-char 1) 2174 | (js2-reparse) 2175 | (call-interactively 'rjsx-comment-dwim) 2176 | 2177 | (search-forward "{" (point-max) t 2) 2178 | (backward-char 1) 2179 | (js2-reparse) 2180 | (call-interactively 'rjsx-comment-dwim)) 2181 | 2182 | (rjsx-deftest-comment 2183 | uncomment-dwim-wrapped-expr-vertical-inner 2184 | "( 2185 |
    2188 | {/* {} */} 2189 | {/* [1,2,3].map(String) */} 2190 | { // 2191 | [1,2,3].map(num => ( 2192 | num * num 2193 | )) 2194 | } 2195 | {/* foo */} 2196 |
    2197 | )" 2198 | "( 2199 |
    2202 | {{}} 2203 | {[1,2,3].map(String)} 2204 | { 2205 | [1,2,3].map(num => ( 2206 | num * num 2207 | )) 2208 | } 2209 | {foo} 2210 |
    2211 | )" 2212 | nil 2213 | (search-forward "{") 2214 | (js2-reparse) 2215 | (call-interactively 'rjsx-comment-dwim) 2216 | 2217 | (search-forward "{") 2218 | (js2-reparse) 2219 | (call-interactively 'rjsx-comment-dwim) 2220 | 2221 | (search-forward "{" (point-max) t 2) 2222 | (js2-reparse) 2223 | (call-interactively 'rjsx-comment-dwim) 2224 | 2225 | (search-forward "{") 2226 | (js2-reparse) 2227 | (call-interactively 'rjsx-comment-dwim) 2228 | 2229 | (search-forward "{") 2230 | (js2-reparse) 2231 | (call-interactively 'rjsx-comment-dwim)) 2232 | 2233 | (rjsx-deftest-comment 2234 | uncomment-dwim-wrapped-expr-vertical-outer 2235 | "( 2236 |
    2239 | {/* {} */} 2240 | {/* [1,2,3].map(String) */} 2241 | { 2242 | // [1,2,3].map(num => ( 2243 | num * num 2244 | )) 2245 | } 2246 | {/* foo */} 2247 |
    2248 | )" 2249 | "( 2250 |
    2253 | {} 2254 | [1,2,3].map(String) 2255 | { 2256 | [1,2,3].map(num => ( 2257 | num * num 2258 | )) 2259 | } 2260 | foo 2261 |
    2262 | )" 2263 | nil 2264 | (search-forward "{") 2265 | (backward-char 1) 2266 | (js2-reparse) 2267 | (call-interactively 'rjsx-comment-dwim) 2268 | 2269 | (search-forward "{" (point-max) t 2) 2270 | (backward-char 1) 2271 | (js2-reparse) 2272 | (call-interactively 'rjsx-comment-dwim) 2273 | 2274 | (search-forward "{" (point-max) t 2) 2275 | (backward-char 1) 2276 | (js2-reparse) 2277 | (call-interactively 'rjsx-comment-dwim) 2278 | 2279 | (forward-line 2) 2280 | (js2-reparse) 2281 | (call-interactively 'rjsx-comment-dwim) 2282 | 2283 | (search-forward "{") 2284 | (backward-char 1) 2285 | (js2-reparse) 2286 | (call-interactively 'rjsx-comment-dwim)) 2287 | 2288 | (rjsx-deftest-comment 2289 | comment-dwim-wrapped-expr-empty-expr-horizontal 2290 | "(
    {}{/* */}{/* hello */}
    )" 2291 | "(
    {/* {} */}{}{hello}
    )" 2292 | 'nil 2293 | (search-forward "{") 2294 | (backward-char 1) 2295 | (js2-reparse) 2296 | (call-interactively 'rjsx-comment-dwim) 2297 | 2298 | (search-forward "{" (point-max) t 3) 2299 | (backward-char 1) 2300 | (js2-reparse) 2301 | (call-interactively 'rjsx-comment-dwim) 2302 | 2303 | (search-forward "{" (point-max) t 2) 2304 | (backward-char 1) 2305 | (js2-reparse) 2306 | (call-interactively 'rjsx-comment-dwim)) 2307 | 2308 | (rjsx-deftest-comment 2309 | comment-dwim-wrapped-expr-empty-expr-vertical 2310 | "( 2311 |
    2312 | {} 2313 | {/* */} 2314 | {/* hello */} 2315 |
    2316 | )" 2317 | "( 2318 |
    2319 | {/* {} */} 2320 | 2321 | hello 2322 |
    2323 | )" 2324 | 'nil 2325 | (forward-line 2) 2326 | (js2-reparse) 2327 | (call-interactively 'rjsx-comment-dwim) 2328 | 2329 | (forward-line 1) 2330 | (js2-reparse) 2331 | (call-interactively 'rjsx-comment-dwim) 2332 | 2333 | (forward-line 1) 2334 | (js2-reparse) 2335 | (call-interactively 'rjsx-comment-dwim)) 2336 | 2337 | (rjsx-deftest-comment 2338 | comment-dwim-text-horizontal 2339 | "(
    hello
    )" 2340 | "(/*
    hello
    */)" 2341 | 'rjsx-comment-dwim 2342 | (search-forward "hello")) 2343 | 2344 | (rjsx-deftest-comment 2345 | comment-dwim-text-vertical 2346 | "( 2347 |
    2348 | hello 2349 |
    2350 | )" 2351 | "( 2352 |
    2353 | {/* hello */} 2354 |
    2355 | )" 2356 | 'rjsx-comment-dwim 2357 | (search-forward "hello")) 2358 | 2359 | (rjsx-deftest-comment 2360 | uncomment-dwim-text-horizontal 2361 | "(/*
    hello
    */)" 2362 | "(
    hello
    )" 2363 | 'rjsx-comment-dwim 2364 | (search-forward "hello")) 2365 | 2366 | (rjsx-deftest-comment 2367 | uncomment-dwim-text-vertical 2368 | "( 2369 |
    2370 | {/* hello */} 2371 |
    2372 | )" 2373 | "( 2374 |
    2375 | hello 2376 |
    2377 | )" 2378 | 'rjsx-comment-dwim 2379 | (search-forward "hello")) 2380 | 2381 | (rjsx-deftest-comment 2382 | comment-region-js 2383 | "function foo () { 2384 | for (let i = 0; i < 10; i++) { 2385 | console.log(i); 2386 | } 2387 | } 2388 | " 2389 | "// function foo () { 2390 | // for (let i = 0; i < 10; i++) { 2391 | // console.log(i); 2392 | // } 2393 | // } 2394 | " 2395 | 'comment-region 2396 | (transient-mark-mode 1) 2397 | (set-mark (point)) 2398 | (goto-char (point-max))) 2399 | 2400 | (rjsx-deftest-comment 2401 | uncomment-region-js 2402 | "// function foo () { 2403 | // for (let i = 0; i < 10; i++) { 2404 | // console.log(i); 2405 | // } 2406 | // } 2407 | " 2408 | "function foo () { 2409 | for (let i = 0; i < 10; i++) { 2410 | console.log(i); 2411 | } 2412 | } 2413 | " 2414 | 'uncomment-region 2415 | (transient-mark-mode 1) 2416 | (set-mark (point)) 2417 | (goto-char (point-max))) 2418 | 2419 | (rjsx-deftest-comment 2420 | comment-region-top-level-element 2421 | "( 2422 |
    2423 | )" 2424 | "( 2425 | //
    2426 | )" 2427 | 'comment-region 2428 | (forward-line 1) 2429 | (transient-mark-mode 1) 2430 | (set-mark (point)) 2431 | (end-of-line)) 2432 | 2433 | (rjsx-deftest-comment 2434 | uncomment-region-top-level-element 2435 | "( 2436 | //
    2437 | )" 2438 | "( 2439 |
    2440 | )" 2441 | 'uncomment-region 2442 | (forward-line 1) 2443 | (transient-mark-mode 1) 2444 | (set-mark (point)) 2445 | (end-of-line)) 2446 | 2447 | (rjsx-deftest-comment 2448 | comment-region-nested-element 2449 | "(
    )" 2450 | "(
    {/* */}
    )" 2451 | 'comment-region 2452 | (search-forward "{/* */}
    )" 2462 | "(
    )" 2463 | 'uncomment-region 2464 | (search-forward "{") 2465 | (backward-char 1) 2466 | (transient-mark-mode 1) 2467 | (set-mark (point)) 2468 | (search-forward "}")) 2469 | 2470 | (rjsx-deftest-comment 2471 | comment-region-attr-horizontal 2472 | "(
    )" 2473 | "(
    )" 2474 | 'comment-region 2475 | (search-forward "c") 2476 | (backward-char 1) 2477 | (transient-mark-mode 1) 2478 | (set-mark (point)) 2479 | (search-forward "\"\"")) 2480 | 2481 | (rjsx-deftest-comment 2482 | comment-region-attr-vertical 2483 | "( 2484 |
    2487 | )" 2488 | "( 2489 |
    2492 | )" 2493 | 'comment-region 2494 | (forward-line 2) 2495 | (transient-mark-mode 1) 2496 | (set-mark (point)) 2497 | (end-of-line)) 2498 | 2499 | (rjsx-deftest-comment 2500 | uncomment-region-attr-horizontal 2501 | "(
    )" 2502 | "(
    )" 2503 | 'uncomment-region 2504 | (search-forward "/*") 2505 | (backward-char 2) 2506 | (transient-mark-mode 1) 2507 | (set-mark (point)) 2508 | (search-forward "*/")) 2509 | 2510 | (rjsx-deftest-comment 2511 | uncomment-region-attr-vertical 2512 | "( 2513 |
    2516 | )" 2517 | "( 2518 |
    2521 | )" 2522 | 'uncomment-region 2523 | (forward-line 2) 2524 | (transient-mark-mode 1) 2525 | (set-mark (point)) 2526 | (end-of-line)) 2527 | 2528 | (rjsx-deftest-comment 2529 | comment-region-wrapped-expr-horizontal 2530 | "(
    )" 2531 | "(
    )" 2532 | 'comment-region 2533 | (search-forward "{") 2534 | (backward-char 1) 2535 | (transient-mark-mode 1) 2536 | (set-mark (point)) 2537 | (search-forward "}")) 2538 | 2539 | (rjsx-deftest-comment 2540 | comment-region-wrapped-expr-horizontal-empty 2541 | "(
    {}
    )" 2542 | "(
    {/* {} */}
    )" 2543 | 'comment-region 2544 | (search-forward "{") 2545 | (backward-char 1) 2546 | (transient-mark-mode 1) 2547 | (set-mark (point)) 2548 | (search-forward "}")) 2549 | 2550 | (rjsx-deftest-comment 2551 | comment-region-wrapped-expr-horizontal-empty-comment 2552 | "(
    {/* {} */}
    )" 2553 | "(
    {/* {/\\* {} *\\/} */}
    )" 2554 | 'comment-region 2555 | (search-forward "{/*") 2556 | (backward-char 3) 2557 | (transient-mark-mode 1) 2558 | (set-mark (point)) 2559 | (search-forward "*/}")) 2560 | 2561 | (rjsx-deftest-comment 2562 | comment-region-wrapped-expr-vertical-inner 2563 | "( 2564 |
    2565 | { 2566 | [1, 2, 3].map(num => ( 2567 | num * num 2568 | )) 2569 | } 2570 |
    2571 | )" 2572 | "( 2573 |
    2574 | { 2575 | /* [1, 2, 3].map(num => ( */ 2576 | /* num * num */ 2577 | /* )) */ 2578 | } 2579 |
    2580 | )" 2581 | 'comment-region 2582 | (forward-line 3) 2583 | (backward-char 1) 2584 | (transient-mark-mode 1) 2585 | (set-mark (point)) 2586 | (forward-line 4)) 2587 | 2588 | (rjsx-deftest-comment 2589 | comment-region-wrapped-expr-vertical-outer 2590 | "( 2591 |
    2592 | { 2593 | [1, 2, 3].map(num => ( 2594 | num * num 2595 | )) 2596 | } 2597 |
    2598 | )" 2599 | "( 2600 |
    2601 | {/* { */} 2602 | {/* [1, 2, 3].map(num => ( */} 2603 | {/* num * num */} 2604 | {/* )) */} 2605 | {/* } */} 2606 |
    2607 | )" 2608 | 'comment-region 2609 | (forward-line 2) 2610 | (backward-char 1) 2611 | (transient-mark-mode 1) 2612 | (set-mark (point)) 2613 | (forward-line 6)) 2614 | 2615 | (rjsx-deftest-comment 2616 | uncomment-region-wrapped-expr-horizontal 2617 | "(
    )" 2618 | "(
    )" 2619 | 'uncomment-region 2620 | (search-forward "{") 2621 | (backward-char 1) 2622 | (transient-mark-mode 1) 2623 | (set-mark (point)) 2624 | (search-forward "}")) 2625 | 2626 | (rjsx-deftest-comment 2627 | uncomment-region-wrapped-expr-horizontal-empty 2628 | "(
    {/* {} */}
    )" 2629 | "(
    {}
    )" 2630 | 'uncomment-region 2631 | (search-forward "{/*") 2632 | (backward-char 3) 2633 | (transient-mark-mode 1) 2634 | (set-mark (point)) 2635 | (search-forward "*/}")) 2636 | 2637 | (rjsx-deftest-comment 2638 | uncomment-region-wrapped-expr-horizontal-empty-comment 2639 | "(
    {/* {/\\* {} *\\/} */}
    )" 2640 | "(
    {/* {} */}
    )" 2641 | 'uncomment-region 2642 | (search-forward "{/*") 2643 | (backward-char 3) 2644 | (transient-mark-mode 1) 2645 | (set-mark (point)) 2646 | (search-forward "*/}")) 2647 | 2648 | (rjsx-deftest-comment 2649 | uncomment-region-wrapped-expr-vertical-inner 2650 | "( 2651 |
    2652 | { 2653 | /* [1, 2, 3].map(num => ( */ 2654 | /* num * num */ 2655 | /* )) */ 2656 | } 2657 |
    2658 | )" 2659 | "( 2660 |
    2661 | { 2662 | [1, 2, 3].map(num => ( 2663 | num * num 2664 | )) 2665 | } 2666 |
    2667 | )" 2668 | 'uncomment-region 2669 | (forward-line 3) 2670 | (backward-char 1) 2671 | (transient-mark-mode 1) 2672 | (set-mark (point)) 2673 | (forward-line 4)) 2674 | 2675 | (rjsx-deftest-comment 2676 | uncomment-region-wrapped-expr-vertical-outer 2677 | "( 2678 |
    2679 | {/* { */} 2680 | {/* [1, 2, 3].map(num => ( */} 2681 | {/* num * num */} 2682 | {/* )) */} 2683 | {/* } */} 2684 |
    2685 | )" 2686 | "( 2687 |
    2688 | { 2689 | [1, 2, 3].map(num => ( 2690 | num * num 2691 | )) 2692 | } 2693 |
    2694 | )" 2695 | 'uncomment-region 2696 | (forward-line 2) 2697 | (backward-char 1) 2698 | (transient-mark-mode 1) 2699 | (set-mark (point)) 2700 | (forward-line 6)) 2701 | 2702 | (rjsx-deftest-comment 2703 | comment-region-text-horizontal 2704 | "(
    hello
    )" 2705 | "(
    {/* hello */}
    )" 2706 | 'comment-region 2707 | (search-forward "h") 2708 | (backward-char 1) 2709 | (transient-mark-mode 1) 2710 | (set-mark (point)) 2711 | (search-forward "o")) 2712 | 2713 | (rjsx-deftest-comment 2714 | comment-region-text-vertical 2715 | "( 2716 |
    2717 | hello 2718 |
    2719 | )" 2720 | "( 2721 |
    2722 | {/* hello */} 2723 |
    2724 | )" 2725 | 'comment-region 2726 | (forward-line 2) 2727 | (transient-mark-mode 1) 2728 | (set-mark (point)) 2729 | (end-of-line)) 2730 | 2731 | (rjsx-deftest-comment 2732 | uncomment-region-text-horizontal 2733 | "(
    {/* hello */}
    )" 2734 | "(
    hello
    )" 2735 | 'uncomment-region 2736 | (search-forward "{") 2737 | (backward-char 1) 2738 | (transient-mark-mode 1) 2739 | (set-mark (point)) 2740 | (search-forward "}")) 2741 | 2742 | (rjsx-deftest-comment 2743 | uncomment-region-text-vertical 2744 | "( 2745 |
    2746 | {/* hello */} 2747 |
    2748 | )" 2749 | "( 2750 |
    2751 | hello 2752 |
    2753 | )" 2754 | 'uncomment-region 2755 | (forward-line 2) 2756 | (transient-mark-mode 1) 2757 | (set-mark (point)) 2758 | (end-of-line)) 2759 | 2760 | (rjsx-deftest-comment 2761 | comment-quote-nested 2762 | "( 2763 |
    2764 | {/* hello */} 2765 |
    2766 | )" 2767 | "( 2768 |
    2769 | {/* {/\\* hello *\\/} */} 2770 |
    2771 | )" 2772 | 'comment-region 2773 | (forward-line 2) 2774 | (transient-mark-mode 1) 2775 | (set-mark (point)) 2776 | (end-of-line)) 2777 | 2778 | (rjsx-deftest-comment 2779 | uncomment-quote-nested 2780 | "( 2781 |
    2782 | {/* {/\\* hello *\\/} */} 2783 |
    2784 | )" 2785 | "( 2786 |
    2787 | {/* hello */} 2788 |
    2789 | )" 2790 | 'uncomment-region 2791 | (forward-line 2) 2792 | (transient-mark-mode 1) 2793 | (set-mark (point)) 2794 | (end-of-line)) 2795 | 2796 | 2797 | ;;; rjsx-tests.el ends here 2798 | --------------------------------------------------------------------------------