aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lisp/progmodes/js.el307
1 files changed, 165 insertions, 142 deletions
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
index 220cf97fdca..af83e04df42 100644
--- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -584,6 +584,29 @@ be buffer-local when in `js-jsx-mode'."
584 :safe 'booleanp 584 :safe 'booleanp
585 :group 'js) 585 :group 'js)
586 586
587(defcustom js-jsx-attribute-offset 0
588 "Specifies a delta for JSXAttribute indentation.
589
590Let `js-indent-level' be 2. When this variable is also set to 0,
591JSXAttribute indentation looks like this:
592
593 <element
594 attribute=\"value\">
595 </element>
596
597Alternatively, when this variable is also set to 2, JSXAttribute
598indentation looks like this:
599
600 <element
601 attribute=\"value\">
602 </element>
603
604This variable is like `sgml-attribute-offset'."
605 :version "27.1"
606 :type 'integer
607 :safe 'integerp
608 :group 'js)
609
587;;; KeyMap 610;;; KeyMap
588 611
589(defvar js-mode-map 612(defvar js-mode-map
@@ -1938,14 +1961,21 @@ the match. Return nil if a match can’t be found."
1938 (setq parens (cdr parens)))) 1961 (setq parens (cdr parens))))
1939 curly-pos)) 1962 curly-pos))
1940 1963
1964(defun js-jsx--goto-outermost-enclosing-curly (limit)
1965 "Set point to enclosing “{” at or closest after LIMIT."
1966 (let (pos)
1967 (while
1968 (and
1969 (setq pos (js-jsx--enclosing-curly-pos))
1970 (if (>= pos limit) (goto-char pos))
1971 (> pos limit)))))
1972
1941(defun js-jsx--enclosing-tag-pos () 1973(defun js-jsx--enclosing-tag-pos ()
1942 "Return beginning and end of a JSXElement about point. 1974 "Return beginning and end of a JSXElement about point.
1943Look backward for a JSXElement that both starts before point and 1975Look backward for a JSXElement that both starts before point and
1944also ends after point. That may be either a self-closing 1976also ends after point. That may be either a self-closing
1945JSXElement or a JSXOpeningElement/JSXClosingElement pair." 1977JSXElement or a JSXOpeningElement/JSXClosingElement pair."
1946 (let ((start (point)) 1978 (let ((start (point)) tag-beg tag-beg-pos tag-end-pos close-tag-pos)
1947 (curly-pos (save-excursion (js-jsx--enclosing-curly-pos)))
1948 tag-beg tag-beg-pos tag-end-pos close-tag-pos)
1949 (while 1979 (while
1950 (and 1980 (and
1951 (setq tag-beg (js--backward-text-property 'js-jsx-tag-beg)) 1981 (setq tag-beg (js--backward-text-property 'js-jsx-tag-beg))
@@ -1957,25 +1987,24 @@ JSXElement or a JSXOpeningElement/JSXClosingElement pair."
1957 (and (eq (car tag-beg) 'self-closing) 1987 (and (eq (car tag-beg) 'self-closing)
1958 (< start tag-end-pos)) 1988 (< start tag-end-pos))
1959 (and (eq (car tag-beg) 'open) 1989 (and (eq (car tag-beg) 'open)
1960 (save-excursion 1990 (or (< start tag-end-pos)
1961 (goto-char tag-end-pos) 1991 (save-excursion
1962 (setq close-tag-pos (js-jsx--matching-close-tag-pos)) 1992 (goto-char tag-end-pos)
1963 ;; The JSXOpeningElement may either be unclosed, 1993 (setq close-tag-pos (js-jsx--matching-close-tag-pos))
1964 ;; else the closure must occur after the start 1994 ;; The JSXOpeningElement may be unclosed, else
1965 ;; point (otherwise, a miscellaneous previous 1995 ;; the closure must occur at/after the start
1966 ;; JSXOpeningElement has been found, and we should 1996 ;; point (otherwise, a miscellaneous previous
1967 ;; keep looking back for an enclosing one). 1997 ;; JSXOpeningElement has been found, so keep
1968 (or (not close-tag-pos) (< start close-tag-pos)))))))) 1998 ;; looking backwards for an enclosing one).
1969 ;; Don’t return the last tag pos (if any; it wasn’t enclosing). 1999 (or (not close-tag-pos) (<= start close-tag-pos)))))))))
1970 (setq tag-beg nil)) 2000 ;; Don’t return the last tag pos, as it wasn’t enclosing.
1971 (and tag-beg 2001 (setq tag-beg nil close-tag-pos nil))
1972 (or (not curly-pos) (> tag-beg-pos curly-pos)) 2002 (and tag-beg (list tag-beg-pos tag-end-pos close-tag-pos))))
1973 (cons tag-beg-pos tag-end-pos))))
1974 2003
1975(defun js-jsx--at-enclosing-tag-child-p () 2004(defun js-jsx--at-enclosing-tag-child-p ()
1976 "Return t if point is at an enclosing tag’s child." 2005 "Return t if point is at an enclosing tag’s child."
1977 (let ((pos (save-excursion (js-jsx--enclosing-tag-pos)))) 2006 (let ((pos (save-excursion (js-jsx--enclosing-tag-pos))))
1978 (and pos (>= (point) (cdr pos))))) 2007 (and pos (>= (point) (nth 1 pos)))))
1979 2008
1980(defun js-jsx--text-range (beg end) 2009(defun js-jsx--text-range (beg end)
1981 "Identify JSXText within a “>/{/}/<” pair." 2010 "Identify JSXText within a “>/{/}/<” pair."
@@ -2515,6 +2544,118 @@ current line is the \"=>\" token."
2515 (t (looking-at-p 2544 (t (looking-at-p
2516 (concat js--name-re js--line-terminating-arrow-re))))) 2545 (concat js--name-re js--line-terminating-arrow-re)))))
2517 2546
2547(defun js-jsx--context ()
2548 "Determine JSX context and move to enclosing JSX."
2549 (let ((pos (point))
2550 (parse-status (syntax-ppss))
2551 (enclosing-tag-pos (js-jsx--enclosing-tag-pos)))
2552 (when enclosing-tag-pos
2553 (if (< pos (nth 1 enclosing-tag-pos))
2554 (if (nth 3 parse-status)
2555 (list 'string (nth 8 parse-status))
2556 (list 'tag (nth 0 enclosing-tag-pos) (nth 1 enclosing-tag-pos)))
2557 (list 'text (nth 0 enclosing-tag-pos) (nth 2 enclosing-tag-pos))))))
2558
2559(defvar js-jsx--indenting nil
2560 "Flag to prevent infinite recursion while indenting JSX.")
2561
2562(defun js-jsx--indentation (parse-status)
2563 "Helper function for `js--proper-indentation'.
2564Return the proper indentation of the current line if it is part
2565of a JSXElement expression spanning multiple lines; otherwise,
2566return nil."
2567 (let ((current-line (line-number-at-pos))
2568 (curly-pos (js-jsx--enclosing-curly-pos))
2569 nth-context context expr-p beg-line col
2570 forward-sexp-function) ; Use the Lisp version.
2571 ;; Find the immediate context for indentation information, but
2572 ;; keep going to determine that point is at the N+1th line of
2573 ;; multiline JSX.
2574 (save-excursion
2575 (while
2576 (and
2577 (setq nth-context (js-jsx--context))
2578 (progn
2579 (unless context
2580 (setq context nth-context)
2581 (setq expr-p (and curly-pos (< (point) curly-pos))))
2582 (setq beg-line (line-number-at-pos))
2583 (and
2584 (= beg-line current-line)
2585 (or (not curly-pos) (> (point) curly-pos)))))))
2586 (when (and context (> current-line beg-line))
2587 (save-excursion
2588 ;; The column calculation is based on `sgml-calculate-indent'.
2589 (setq col (pcase (nth 0 context)
2590
2591 ('string
2592 ;; Go back to previous non-empty line.
2593 (while (and (> (point) (nth 1 context))
2594 (zerop (forward-line -1))
2595 (looking-at "[ \t]*$")))
2596 (if (> (point) (nth 1 context))
2597 ;; Previous line is inside the string.
2598 (current-indentation)
2599 (goto-char (nth 1 context))
2600 (1+ (current-column))))
2601
2602 ('tag
2603 ;; Special JSX indentation rule: a “dangling”
2604 ;; closing angle bracket on its own line is
2605 ;; indented at the same level as the opening
2606 ;; angle bracket of the JSXElement. Otherwise,
2607 ;; indent JSXAttribute space like SGML.
2608 (if (progn
2609 (goto-char (nth 2 context))
2610 (and (= current-line (line-number-at-pos))
2611 (looking-back "^\\s-*/?>" (line-beginning-position))))
2612 (progn
2613 (goto-char (nth 1 context))
2614 (current-column))
2615 ;; Indent JSXAttribute space like SGML.
2616 (goto-char (nth 1 context))
2617 ;; Skip tag name:
2618 (skip-chars-forward " \t")
2619 (skip-chars-forward "^ \t\n")
2620 (skip-chars-forward " \t")
2621 (if (not (eolp))
2622 (current-column)
2623 ;; This is the first attribute: indent.
2624 (goto-char (+ (nth 1 context) js-jsx-attribute-offset))
2625 (+ (current-column) js-indent-level))))
2626
2627 ('text
2628 ;; Indent to reflect nesting.
2629 (goto-char (nth 1 context))
2630 (+ (current-column)
2631 ;; The last line isn’t nested, but the rest are.
2632 (if (or (not (nth 2 context)) ; Unclosed.
2633 (< current-line (line-number-at-pos (nth 2 context))))
2634 js-indent-level
2635 0)))
2636
2637 )))
2638 ;; When indenting a JSXExpressionContainer expression, use JSX
2639 ;; indentation as a minimum, and use regular JS indentation if
2640 ;; it’s deeper.
2641 (if expr-p
2642 (max (+ col
2643 ;; An expression in a JSXExpressionContainer in a
2644 ;; JSXAttribute should be indented more, except on
2645 ;; the ending line of the JSXExpressionContainer.
2646 (if (and (eq (nth 0 context) 'tag)
2647 (< current-line
2648 (save-excursion
2649 (js-jsx--goto-outermost-enclosing-curly
2650 (nth 1 context))
2651 (forward-sexp)
2652 (line-number-at-pos))))
2653 js-indent-level
2654 0))
2655 (let ((js-jsx--indenting t)) ; Prevent recursion.
2656 (js--proper-indentation parse-status)))
2657 col))))
2658
2518(defun js--proper-indentation (parse-status) 2659(defun js--proper-indentation (parse-status)
2519 "Return the proper indentation for the current line." 2660 "Return the proper indentation for the current line."
2520 (save-excursion 2661 (save-excursion
@@ -2522,6 +2663,8 @@ current line is the \"=>\" token."
2522 (cond ((nth 4 parse-status) ; inside comment 2663 (cond ((nth 4 parse-status) ; inside comment
2523 (js--get-c-offset 'c (nth 8 parse-status))) 2664 (js--get-c-offset 'c (nth 8 parse-status)))
2524 ((nth 3 parse-status) 0) ; inside string 2665 ((nth 3 parse-status) 0) ; inside string
2666 ((when (and js-jsx-syntax (not js-jsx--indenting))
2667 (save-excursion (js-jsx--indentation parse-status))))
2525 ((eq (char-after) ?#) 0) 2668 ((eq (char-after) ?#) 0)
2526 ((save-excursion (js--beginning-of-macro)) 4) 2669 ((save-excursion (js--beginning-of-macro)) 4)
2527 ;; Indent array comprehension continuation lines specially. 2670 ;; Indent array comprehension continuation lines specially.
@@ -2584,111 +2727,6 @@ current line is the \"=>\" token."
2584 (+ js-indent-level js-expr-indent-offset)) 2727 (+ js-indent-level js-expr-indent-offset))
2585 (t (prog-first-column))))) 2728 (t (prog-first-column)))))
2586 2729
2587;;; JSX Indentation
2588
2589(defmacro js-jsx--as-sgml (&rest body)
2590 "Execute BODY as if in sgml-mode."
2591 `(with-syntax-table sgml-mode-syntax-table
2592 ,@body))
2593
2594(defun js-jsx--outermost-enclosing-tag-pos ()
2595 (let (context tag-pos last-tag-pos parse-status parens paren-pos curly-pos)
2596 (js-jsx--as-sgml
2597 ;; Search until we reach the top or encounter the start of a
2598 ;; JSXExpressionContainer (implying nested JSX).
2599 (while (and (setq context (sgml-get-context))
2600 (progn
2601 (setq tag-pos (sgml-tag-start (car (last context))))
2602 (or (not curly-pos)
2603 ;; Stop before curly brackets (start of a
2604 ;; JSXExpressionContainer).
2605 (> tag-pos curly-pos))))
2606 ;; Record this position so it can potentially be returned.
2607 (setq last-tag-pos tag-pos)
2608 ;; Always parse sexps / search for the next context from the
2609 ;; immediately enclosing tag (sgml-get-context may not leave
2610 ;; point there).
2611 (goto-char tag-pos)
2612 (unless parse-status ; Don’t needlessly reparse.
2613 ;; Search upward for an enclosing starting curly bracket.
2614 (setq parse-status (syntax-ppss))
2615 (setq parens (reverse (nth 9 parse-status)))
2616 (while (and (setq paren-pos (car parens))
2617 (not (when (= (char-after paren-pos) ?{)
2618 (setq curly-pos paren-pos))))
2619 (setq parens (cdr parens)))
2620 ;; Always search for the next context from the immediately
2621 ;; enclosing tag (calling syntax-ppss in the above loop
2622 ;; may move point from there).
2623 (goto-char tag-pos))))
2624 last-tag-pos))
2625
2626(defun js-jsx--indentation-type ()
2627 "Determine if/how the current line should be indented as JSX.
2628
2629Return nil for first JSXElement line (indent like JS).
2630Return `n+1th' for second+ JSXElement lines (indent like SGML).
2631Return `expression' for lines within embedded JS expressions
2632 (indent like JS inside SGML).
2633Return nil for non-JSX lines."
2634 (let ((current-pos (point))
2635 (current-line (line-number-at-pos))
2636 tag-start-pos parens paren type)
2637 (save-excursion
2638 ;; Determine if inside a JSXElement.
2639 (beginning-of-line) ; For exclusivity
2640 (when (setq tag-start-pos (js-jsx--outermost-enclosing-tag-pos))
2641 ;; Check if inside an embedded multi-line JS expression.
2642 (goto-char current-pos)
2643 (end-of-line) ; For exclusivity
2644 (setq parens (nth 9 (syntax-ppss)))
2645 (while
2646 (and
2647 (setq paren (car parens))
2648 (if (and
2649 (>= paren tag-start-pos)
2650 ;; A curly bracket indicates the start of an
2651 ;; embedded expression.
2652 (= (char-after paren) ?{)
2653 ;; The first line of the expression is indented
2654 ;; like SGML.
2655 (> current-line (line-number-at-pos paren))
2656 ;; Check if within a closing curly bracket (if any)
2657 ;; (exclusive, as the closing bracket is indented
2658 ;; like SGML).
2659 (if (progn
2660 (goto-char paren)
2661 (ignore-errors (let (forward-sexp-function)
2662 (forward-sexp))))
2663 (< current-line (line-number-at-pos))
2664 ;; No matching bracket implies we’re inside!
2665 t))
2666 ;; Indicate this will be indented specially. Return
2667 ;; nil to stop iterating too.
2668 (progn (setq type 'expression) nil)
2669 ;; Stop iterating when parens = nil.
2670 (setq parens (cdr parens)))))
2671 (or type 'n+1th)))))
2672
2673(defun js-jsx--indent-line-in-expression ()
2674 "Indent the current line as JavaScript within JSX."
2675 (let ((parse-status (save-excursion (syntax-ppss (point-at-bol))))
2676 offset indent-col)
2677 (unless (nth 3 parse-status)
2678 (save-excursion
2679 (setq offset (- (point) (progn (back-to-indentation) (point)))
2680 indent-col (js-jsx--as-sgml (sgml-calculate-indent))))
2681 (if (null indent-col) 'noindent ; Like in sgml-mode
2682 ;; Use whichever indentation column is greater, such that the
2683 ;; SGML column is effectively a minimum.
2684 (indent-line-to (max (js--proper-indentation parse-status)
2685 (+ indent-col js-indent-level)))
2686 (when (> offset 0) (forward-char offset))))))
2687
2688(defun js-jsx--indent-n+1th-line ()
2689 "Indent the current line as JSX within JavaScript."
2690 (js-jsx--as-sgml (sgml-indent-line)))
2691
2692(defun js-indent-line () 2730(defun js-indent-line ()
2693 "Indent the current line as JavaScript." 2731 "Indent the current line as JavaScript."
2694 (interactive) 2732 (interactive)
@@ -2700,15 +2738,9 @@ Return nil for non-JSX lines."
2700 (when (> offset 0) (forward-char offset))))) 2738 (when (> offset 0) (forward-char offset)))))
2701 2739
2702(defun js-jsx-indent-line () 2740(defun js-jsx-indent-line ()
2703 "Indent the current line as JSX (with SGML offsets). 2741 "Indent the current line as JavaScript+JSX."
2704i.e., customize JSX element indentation with `sgml-basic-offset',
2705`sgml-attribute-offset' et al."
2706 (interactive) 2742 (interactive)
2707 (let ((type (js-jsx--indentation-type))) 2743 (let ((js-jsx-syntax t)) (js-indent-line)))
2708 (if type
2709 (if (eq type 'n+1th) (js-jsx--indent-n+1th-line)
2710 (js-jsx--indent-line-in-expression))
2711 (js-indent-line))))
2712 2744
2713;;; Filling 2745;;; Filling
2714 2746
@@ -4281,18 +4313,9 @@ If one hasn't been set, or if it's stale, prompt for a new one."
4281 4313
4282;;;###autoload 4314;;;###autoload
4283(define-derived-mode js-jsx-mode js-mode "JSX" 4315(define-derived-mode js-jsx-mode js-mode "JSX"
4284 "Major mode for editing JSX. 4316 "Major mode for editing JSX."
4285
4286To customize the indentation for this mode, set the SGML offset
4287variables (`sgml-basic-offset', `sgml-attribute-offset' et al.)
4288locally, like so:
4289
4290 (defun set-jsx-indentation ()
4291 (setq-local sgml-basic-offset js-indent-level))
4292 (add-hook \\='js-jsx-mode-hook #\\='set-jsx-indentation)"
4293 :group 'js 4317 :group 'js
4294 (setq-local js-jsx-syntax t) 4318 (setq-local js-jsx-syntax t))
4295 (setq-local indent-line-function #'js-jsx-indent-line))
4296 4319
4297;;;###autoload (defalias 'javascript-mode 'js-mode) 4320;;;###autoload (defalias 'javascript-mode 'js-mode)
4298 4321