3

Using javascript-mode, functions are indented like this:

console.log('Example',
            var1,
            var2);

test('Example2',
     var1,
     var2);

I'd like to change this so that subsequent lines are intended by one tab instead, which is represented here with two spaces:

console.log('Example',
  var1,
  var2);

test('Example2',
  var1,
  var2);

How can this be done? I can't find any options for javascript-mode that control this part of the indent process.

3

The code that handles this is the function js--proper-indentation, based on analysis from syntax-ppss.

Easiest solution (if you can tolerate it) is to write your code like

console.log(
    'Example',
    var1,
    var2);

Otherwise, you're going to have to modify the code. (I see no customization option in js.el.)

Specifically, you need to do something different from this:

;; If there is something following the opening
;; paren/bracket, everything else should be indented at
;; the same level.
(unless same-indent-p
  (forward-char)
  (skip-chars-forward " \t"))
(current-column))))

If you feel adventuresome, you might try modifying that function. In very brief experimentation, this seemed to work:

Find the conditional that begins with this line

(if (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)")

If you remove the if (that whole line above) and the s-expressions that are the else part of the if:

;; If there is something following the opening
;; paren/bracket, everything else should be indented at
;; the same level.
(unless same-indent-p
  (forward-char)
  (skip-chars-forward " \t"))
(current-column)) ;; <- that is the end of the "if"
))                ;; keep those last two close parens.

then, I think you will just get your normal indention level for lines after the start of a function. That worked for me.

Here is my modified defun which you can use as :override advice:

(defun my-js--proper-indentation (parse-status)
  "Return the proper indentation for the current line."
  (save-excursion
    (back-to-indentation)
    (cond ((nth 4 parse-status)    ; inside comment
           (js--get-c-offset 'c (nth 8 parse-status)))
          ((nth 3 parse-status) 0) ; inside string
          ((eq (char-after) ?#) 0)
          ((save-excursion (js--beginning-of-macro)) 4)
          ;; Indent array comprehension continuation lines specially.
          ((let ((bracket (nth 1 parse-status))
                 beg)
             (and bracket
                  (not (js--same-line bracket))
                  (setq beg (js--indent-in-array-comp bracket))
                  ;; At or after the first loop?
                  (>= (point) beg)
                  (js--array-comp-indentation bracket beg))))
          ((js--ctrl-statement-indentation))
          ((js--multi-line-declaration-indentation))
          ((nth 1 parse-status)
       ;; A single closing paren/bracket should be indented at the
       ;; same level as the opening statement. Same goes for
       ;; "case" and "default".
           (let ((same-indent-p (looking-at "[]})]"))
                 (switch-keyword-p (looking-at "default\\_>\\|case\\_>[^:]"))
                 (continued-expr-p (js--continued-expression-p)))
             (goto-char (nth 1 parse-status)) ; go to the opening char
             (progn ; nothing following the opening paren/bracket
               (skip-syntax-backward " ")
               (when (eq (char-before) ?\)) (backward-list))
               (back-to-indentation)
               (js--maybe-goto-declaration-keyword-end parse-status)
               (let* ((in-switch-p (unless same-indent-p
                                     (looking-at "\\_<switch\\_>")))
                      (same-indent-p (or same-indent-p
                                         (and switch-keyword-p
                                              in-switch-p)))
                      (indent
                       (cond (same-indent-p
                              (current-column))
                             (continued-expr-p
                              (+ (current-column) (* 2 js-indent-level)
                                 js-expr-indent-offset))
                             (t
                              (+ (current-column) js-indent-level
                                 (pcase (char-after (nth 1 parse-status))
                                   (?\( js-paren-indent-offset)
                                   (?\[ js-square-indent-offset)
                                   (?\{ js-curly-indent-offset)))))))
                 (if in-switch-p
                     (+ indent js-switch-indent-offset)
                   indent)))))

          ((js--continued-expression-p)
           (+ js-indent-level js-expr-indent-offset))
          (t 0))))

(advice-add 'js--proper-indentation :override 'my-js--proper-indentation)
2

I found that Colin's answer fixed some instances of strange JS indentation, but not all of them. I've tweaked his code further to get indentation behavior that doesn't produce errors when linting (using flycheck and eslint) against the Air-BNB style guide. My code is below; here are a few notes:

  • The if in the original js--proper-indentation function (do a M-x describe-function to get to the source code) checks if the most recent open symbol occurs at the end of the line, and if so uses the two-space indentation we want. Therefore, in the code below we get rid of the if and unless and leave just the code inside the if. (This fix is thanks to Colin's answer.)

  • The (js--multi-line-declaration-indentation) call in the original code sometimes lines up symbols and causes an indentation of more than two spaces, for example in a long const declaration. I've removed it, allowing the later conditions to handle this case.

  • The (continued-expr-p) case within the inner let* sometimes causes an indentation of more than two characters in the original code. This is because it multiplies js-indent-level by 2, which doesn't make sense, since js-indent-level is already a multiple of 2 representing the number of spaces of indentation. I've removed this multiplication by 2 to fix the issue.

  • The (nth 1 parse-status) goes back to the most recent open symbol in the original code. This is what we want if we're in a continued expression including open symbols, but in long expressions without open symbols this goes all the way back to, say, the beginning of the function we're in, which results in a continued expression without a hanging indent. I've fixed this with the code in the inner continued-expr-p condition, which goes back to the last open symbol or the beginning of the continued expression, whichever occurs later.

Here is the code, which can be added to your init.el:

(defun js--proper-indentation-custom (parse-status)
  "Return the proper indentation for the current line according to PARSE-STATUS argument."
  (save-excursion
    (back-to-indentation)
    (cond ((nth 4 parse-status)    ; inside comment
           (js--get-c-offset 'c (nth 8 parse-status)))
          ((nth 3 parse-status) 0) ; inside string
          ((eq (char-after) ?#) 0)
          ((save-excursion (js--beginning-of-macro)) 4)
          ;; Indent array comprehension continuation lines specially.
          ((let ((bracket (nth 1 parse-status))
                 beg)
             (and bracket
                  (not (js--same-line bracket))
                  (setq beg (js--indent-in-array-comp bracket))
                  ;; At or after the first loop?
                  (>= (point) beg)
                  (js--array-comp-indentation bracket beg))))
          ((js--ctrl-statement-indentation))
          ((nth 1 parse-status)
           ;; A single closing paren/bracket should be indented at the
           ;; same level as the opening statement. Same goes for
           ;; "case" and "default".
           (let ((same-indent-p (looking-at "[]})]"))
                 (switch-keyword-p (looking-at "default\\_>\\|case\\_>[^:]"))
                 (continued-expr-p (js--continued-expression-p))
                 (original-point (point))
                 (open-symbol (nth 1 parse-status)))
             (goto-char (nth 1 parse-status)) ; go to the opening char
             (skip-syntax-backward " ")
             (when (eq (char-before) ?\)) (backward-list))
             (back-to-indentation)
             (js--maybe-goto-declaration-keyword-end parse-status)
             (let* ((in-switch-p (unless same-indent-p
                                   (looking-at "\\_<switch\\_>")))
                    (same-indent-p (or same-indent-p
                                       (and switch-keyword-p
                                            in-switch-p)))
                    (indent
                     (cond (same-indent-p
                            (current-column))
                           (continued-expr-p
                            (goto-char original-point)
                            ;; Go to beginning line of continued expression.
                            (while (js--continued-expression-p)
                              (forward-line -1))
                            ;; Go to the open symbol if it appears later.
                            (when (> open-symbol (point))
                              (goto-char open-symbol))
                            (back-to-indentation)
                            (+ (current-column)
                               js-indent-level
                               js-expr-indent-offset))
                           (t
                            (+ (current-column) js-indent-level
                               (pcase (char-after (nth 1 parse-status))
                                 (?\( js-paren-indent-offset)
                                 (?\[ js-square-indent-offset)
                                 (?\{ js-curly-indent-offset)))))))
               (if in-switch-p
                   (+ indent js-switch-indent-offset)
                 indent))))
          ((js--continued-expression-p)
           (+ js-indent-level js-expr-indent-offset))
          (t 0))))

(advice-add 'js--proper-indentation :override 'js--proper-indentation-custom)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.