Repository navigation
Fix the indent spec conversions and match cljfmt's specs - #709
Merged
Merged
Conversation
clojure--indent-spec-to-modern turned each element of a positional
spec into a rule on its own, dropping the position an element stands
for and counting the depth of a nested spec from the wrong level:
(1 ((:defn)) nil) letfn became ((:block 1) (:inner 2))
(:defn (1)) reify became ((:inner 0))
(1 :defn) extend-protocol became ((:block 1) (:inner 0))
Read the elements the way the backtracking indentation does instead:
the first one is the spec of the form itself and the rest those of its
arguments, the last one also covering the arguments after it. That
gives ((:block 1) (:inner 2 0)), ((:inner 0) (:inner 1)) and
((:block 1) (:inner 1)), the rules clojure-ts-mode and cljfmt use for
those forms. clojure-ts-mode has used the same conversion for the
specs it gets from CIDER since clojure-emacs/clojure-ts-mode#162.
clojure--indent-spec-to-legacy, which turns a modern spec into one the backtracking indentation can use, got a few things wrong: - An :inner rule with a position went to that position in the list when there was no :block rule, but position 0 is the form itself, so ((:inner 1 0)) became ((:defn) nil) and indenting such a form signaled an error. - An (:inner 0) rule went at the end, where it applied to the arguments, so ((:block 1) (:inner 0)) indented the methods of extend-protocol like (:inner 1) would. The built-in specs of extend-protocol, extend-type, specify and specify! relied on that, and clojure-ts-mode and cljfmt read them differently. - Several :inner rules without a position each took a position of their own, and only the last applied to the arguments after them. Reserve position 0 for the :block rule or (:inner 0), put the rules with a position after it, and end with the shallowest rule without one. Give the four forms above the (:inner 1) rule they mean.
cljfmt indents clojure.spec's fdef with [[:inner 0]], so a name on a line of its own gets 2 spaces, where the (:block 1) rule gave it 4. It also has let* as [[:block 1]], which clojure-mode only got through its rule for symbols that start with let. clojure-ts-mode has both.
cljfmt has rules for match, struct-map, defstruct, the with- forms and a few def forms that clojure-mode had no spec for. The def and with- forms got body indentation from the rule for symbols with those prefixes, so a with-open whose bindings were on a line of their own indented them as a body, unlike let. match and struct-map got none and were indented like function calls. Use cljfmt's rules, which clojure-ts-mode uses too.
bbatsov
force-pushed
the
fix-indent-spec-conversion
branch
2 times, most recently
from
November 1, 2026 09:35
42b6fc4 to
4ca3026
Compare
bbatsov
added a commit
to clojure-emacs/cider
that referenced
this pull request
Nov 1, 2026
cider--indent-spec-to-legacy turns the modern :style/indent specs from cider-nrepl into the legacy format, which both clojure-mode and clojure-ts-mode accept. It got a few things wrong: - An :inner rule with a position went to that position in the list when there was no :block rule, but position 0 is the form itself, so [[:inner 1 0]] became ((:defn) nil). clojure-mode signaled an error indenting such a form, and clojure-ts-mode found no rules in it. - An (:inner 0) rule went at the end, where it applied to the arguments, so [[:block 1] [:inner 0]] indented them like [:inner 1] would. cljfmt ignores [:inner 0] next to a :block rule. - Several :inner rules without a position each took a position of their own, and only the last applied to the arguments after them. Reserve position 0 for the :block rule or (:inner 0), put the rules with a position after it, and end with the shallowest rule without one. clojure-mode's own copy of this conversion gets the same fix in clojure-emacs/clojure-mode#709.
bbatsov
added a commit
to clojure-emacs/cider
that referenced
this pull request
Nov 1, 2026
cider--indent-spec-to-legacy turns the modern :style/indent specs from cider-nrepl into the legacy format, which both clojure-mode and clojure-ts-mode accept. It got a few things wrong: - An :inner rule with a position went to that position in the list when there was no :block rule, but position 0 is the form itself, so [[:inner 1 0]] became ((:defn) nil). clojure-mode signaled an error indenting such a form, and clojure-ts-mode found no rules in it. - An (:inner 0) rule went at the end, where it applied to the arguments, so [[:block 1] [:inner 0]] indented them like [:inner 1] would. cljfmt ignores [:inner 0] next to a :block rule. - Several :inner rules without a position each took a position of their own, and only the last applied to the arguments after them. Reserve position 0 for the :block rule or (:inner 0), put the rules with a position after it, and end with the shallowest rule without one. clojure-mode's own copy of this conversion gets the same fix in clojure-emacs/clojure-mode#709.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The conversions between the legacy and modern indent spec formats dropped the positions of
:innerrules and got their depths wrong, and a modern spec like((:inner 1 0))made indentation signal an error. With those fixed, the built-in specs now match the ones clojure-ts-mode and cljfmt use.extend-protocoland friends get(:inner 1), since their(:inner 0)only worked through the old conversion, so specs copied from the 5.23 README need the same change.