Giter Club home page Giter Club logo

cljstyle's Introduction

cljstyle

CircleCI codecov

cljstyle is a tool for formatting Clojure code. It can take something messy like this:

(  ns
 foo.bar.baz  "some doc"
    (:require (foo.bar [abc :as abc]
        def))
    (:use foo.bar.qux)
    (:import foo.bar.qux.Foo
      ;; Need this for the thing
      foo.bar.qux.Bar)
    )

(defn hello "says hi" (
      [] (hello "world")
  ) ([name]
  ( println "Hello," name  )
  ))

...and restyle it into nicely-formatted code like this:

(ns foo.bar.baz
  "some doc"
  (:require
    [foo.bar.abc :as abc]
    [foo.bar.def]
    [foo.bar.qux :refer :all])
  (:import
    (foo.bar.qux
      ;; Need this for the thing
      Bar
      Foo)))


(defn hello
  "says hi"
  ([] (hello "world"))
  ([name]
   (println "Hello," name)))

Note that this is a rewrite of the original weavejester/cljfmt tool to provide more capabilities and configurability as well as a native-compiled binary.

Installation

Manual install

Binary releases are available on the GitHub project. The native binaries are self-contained, so to install them simply place them on your path.

Installation script (macOS and Linux)

This installation script works for linux and MacOS and can be used for quickly installing or upgrading to the newest cljstyle without a package manager. It will install to /usr/local/bin by default.

To download and execute the script:

curl -sLO https://raw.githubusercontent.com/greglook/cljstyle/main/util/install-cljstyle
chmod +x install-cljstyle
./install-cljstyle

The script accepts several options to control the installation directory, download directory, version, and architecture. Run with --help to see all options. To upgrade, just run the script again.

macOS via Homebrew

cljstyle can be installed on macOS via a Homebrew Cask:

brew install --cask cljstyle

Clojars

Releases are also published to Clojars. To use the latest version, add the following dependency to your project:

Clojars Project

Usage

The cljstyle tool supports several different commands for checking source files.

Check and Fix

To check the formatting of your source files, use:

cljstyle check

If the formatting of any source file is incorrect, a diff will be supplied showing the problem, and what cljstyle thinks it should be.

If you want to check only a specific file, or several specific files, you can do that, too:

cljstyle check src/foo/core.clj

Once you've identified formatting issues, you can choose to ignore them, fix them manually, or let cljstyle fix them with:

cljstyle fix

As with the check task, you can choose to fix a specific file:

cljstyle fix src/foo/core.clj

The pipe command offers a generic way to correct style by reading Clojure code from stdin and writing the reformatted code to stdout:

cljstyle pipe < in.clj > out.clj

This command resolves configuration from the directory it is executed in, since there is no explicit file path to use.

Debugging

For inspecting what cljstyle is doing, one tool is to specify the --verbose flag, which will cause additional debugging output to be printed. There are also a few extra commands which can help understand what's happening.

The find command will print what files would be checked by cljstyle. It will print each file path to standard output on a new line:

cljstyle find [path...]

The config command will show what configuration settings cljstyle would use to process the specified files or files in the current directory:

cljstyle config [path]

Finally, version will show what version of the tool you're using:

cljstyle version

Integrations

cljstyle can be integrated into many different tools, including shells, editors, and tests. See the integration docs for more details.

Configuration

The cljstyle tool comes with a sensible set of default configuration built-in and may additionally be configured by using a hierarchy of .cljstyle files in the source tree. The configuration settings include toggles for format rules, width constraints, and the indentation rules.

Ignoring Forms

By default, cljstyle will ignore forms which are wrapped in a (comment ...) form or preceeded by the discard macro #_. You can also optionally disable formatting rules from matching a form by tagging it with ^:cljstyle/ignore metadata - this is often useful for macros.

License

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

cljstyle's People

Contributors

aengelberg avatar agentydragon avatar arrdem avatar brandonvin avatar drewinglis avatar egs33 avatar filipesilva avatar greglook avatar jeroenvandijk avatar jetmind avatar julienba avatar kailashnath avatar maio avatar maskys avatar matthewdarling avatar mwfogleman avatar nikolap avatar noisesmith avatar patrick-sharp-amperity avatar pcarlisle avatar rbxbx avatar robhanlon22 avatar roryokane avatar saitouena avatar tobias avatar tomjkidd avatar ul avatar venantius avatar weavejester avatar wilkerlucio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

cljstyle's Issues

Setup via brew

Hi!
Thanks for the library. 🔥
Any plans for installing with brew? It would be great.

Maps with default namespaces aren't indented correctly

$ cljfmt check foo.clj 
--- a/foo.clj
+++ b/foo.clj
@@ -1,5 +1,5 @@
 (ns foo)
 
 
-(def bar #:asdf{:one 1
+(def bar #:asdf {:one 1
                 :two 2})
1 files formatted incorrectly
$ cat foo.clj
(ns foo)


(def bar #:asdf{:one 1
                :two 2})

Currently it inserts a space between the default namespace and the map, but doesn't indent the map to match.

It should either insert a space and indent the map or do nothing.

Version 0.8.1.

cljc support

Currently it chokes on the :reader-macro conditionals:

java.lang.IllegalArgumentException: No matching clause: :reader-macro

license?

Hi Greg,

I'm looking to package up this utility for Arch (I already maintain a few others, like clj-kondo and babashka). Is there a license for this utility? I can't seem to see it in the README or in the source.

Thanks.

-=david=-

Publish to Clojars

While not as performant as the GraalVM-built binary, publishing cljstyle to Clojars would make integration with existing Clojure build tooling quite a bit easier, and would be well-suited to CI environments. What do you think?

Support for heading and top-level comments

The comment formatting which was introduced in #30 distinguishes between inline and so called "leading comments". However Lisp (and Clojure) uses comment conventions which are using even more semicolons, i.e. Heading and Top-Level comments which are using four and three ; respectively (see https://guide.clojure.style/#four-semicolons-for-heading-comments and https://guide.clojure.style/#three-semicolons-for-top-level-comments). These comments shouldn't be reformatted with two semicolons. So we either need a way to detect these different comment types and provide separate prefix rules for them (i.e. Top-Level comments are usually followed by an empty line) or allow more flexible ways to specify the prefix rules for "leading comments".

`defn-` blocks are skipped

While running cljfmt check/fix on files which have misaligned code in defn- the program passes over it silently without complaining

String ns requires

(ns foo.bar
  (:require
    ["something"]
    ["something" :as something]
    ["something" :default something])

Are all ns declarations that are used often in shadow-cljs that use external JS libs.

I haven't validated :default yet but could be tested alongside this.

Support attr-map in ns form

An ns form like:

(ns foo.bar
   {:baz "baz"})

results in the file being skipped with:
clojure.lang.ExceptionInfo: Unknown ns node form :map

The attr-map is an alternate way to specify metadata for the ns.

I started implementing this, but got stuck on how to correctly transform a node that represents the map. I'm happy to keep working on it, but could use some guidance.

Keep ns :require (and others) linebreaks

I like to keep the first require at the same line with :require form:

(ns example.foo
  (:require [reagent.core :as r]
            [re-frame.core :as rf]))

Currently cljstyle adds linebreak after :require. I'd like to use the ns formatting, but keeping existing linebreaks.

imports should allow `[` as well as `(`

Currently, when checking a file with imports like so:

  (:import
   [org.flywaydb.core 
    Flyway]
   [org.flywaydb.core.api.configuration 
    FluentConfiguration])

cljstyle will complain like so:

-   [org.flywaydb.core
-    Flyway]
-   [org.flywaydb.core.api.configuration
-    FluentConfiguration]))
+   (org.flywaydb.core
+    Flyway)
+   (org.flywaydb.core.api.configuration
+    FluentConfiguration))

i.e., flagging the use of [ as incorrect. It's totally acceptable to perfer [ in keeping with the use of require.

Cljstyle should allow as a configuration option the option to perfer one or the other.

Comment formatting

So far cljstyle mostly avoids moving comments around or otherwise formatting them, but there are a few rules that come to mind as useful additions:

  • Enforce that stand-alone comments start with two semicolons (;;) - this matches convention and keeps Emacs from pushing the comment far to the right as it does with single-semicolon comments.
  • Apply indentation rules to comments. Right now a reindented form leaves comments at their original indent, requiring manual fixup later.
  • Wrap long comments at a certain column.

check task sometimes hangs

This mostly happens in CI, but I've observed sometimes the cljstyle check task hangs with no output for 10+ minutes and gets killed for inactivity. Occasionally we do see some output warning that not all of the threads have completed in the expected time interval.

Investigate, and potentially add some internal state dumping if we go way beyond the expected runtime.

Add align map values

Hi!
Thank you again for the cljstyle!
Can you add align map values?
It would be great!

Something like this:

(def my-map 
  {:a     "A value"
   :blarg :another-value
   :foo   {:test "A nested map"
           :c    :d}})

Documentation improvement suggestions

Hey there!

I just came across the project (which looks pretty cool, btw) and I have a couple of small suggestions to improve the docs:

  • Might be a good idea to publish the docs folder to a service like ReadTheDocs. This requires almost no work from you, just adding a configuration file for the service. I didn't notice the doc files originally and I thought the README was a bit short on details.
  • I think it'd be great if there's some rationale for rewriting cljfmt (which I've been using for a while), as it's not very clear what exactly are the advantages of cljstyle
  • I think it's a good idea to indicate where are the defaults derived from. I'm hoping that's the community style guide, but I couldn't find any indication of this in the docs.

Thanks for the great project! 🙇

Support shebang

Some Clojure files have shebang like #!/usr/bin/env clj -M or #!/usr/bin/env bb (it's https://github.com/babashka/babashka ). cljstyle can't process these files with clojure.lang.ExceptionInfo: Invalid symbol: !/usr/bin/env.. When the first line starts with #! it would be a shebang so it'll be ignored.

Running cljfmt with all rules disabled still produces diffs

The most notable diff is that for named functions it doesn't allow the arguments list to stay on the same line:

$ cljfmt check foo.clj 
--- a/foo.clj
+++ b/foo.clj
@@ -1,2 +1,3 @@
-(fn foo [arg-one]
+(fn foo
+[arg-one]
   nil)
1 files formatted incorrectly
$ cat foo.clj 
(fn foo [arg-one]
  nil)

Running cljfmt with only `: insert-padding-lines?` enabled produces non-blank-line changes

$ cat .cljfmt 
{:max-blank-lines 3
 :single-import-break-width 1000
 :indentation? false
 :remove-surrounding-whitespace? false
 :remove-trailing-whitespace? false
 :insert-missing-whitespace? false
 :remove-consecutive-blank-lines? false
 :insert-padding-lines? true
 :rewrite-namespaces? false}
$ cat foo.clj
(ns foo)

(defn foo-two []
  nil)
$ cljfmt check foo.clj 
--- a/foo.clj
+++ b/foo.clj
@@ -1,4 +1,6 @@
 (ns foo)
 
-(defn foo-two []
+
+(defn foo-two
+  []
   nil)
1 files formatted incorrectly

I would expect the diff to be:

$ cljfmt check foo.clj 
--- a/foo.clj
+++ b/foo.clj
@@ -1,4 +1,6 @@
 (ns foo)
 
+
(defn foo-two []
   nil)
1 files formatted incorrectly

UnsupportedOperationException on this apparently valid cljs

$ cat foo.cljs
(ns foo
  (:import
    [goog.async Debouncer]))
$ cljfmt check -v foo.cljs
,,,
java.lang.UnsupportedOperationException: null                                                           
 at rewrite_clj.node.whitespace.WhitespaceNode.sexpr (whitespace.clj:28)         
    cljfmt.format.ns$expand_imports$fn__2718$fn__2722.invoke (ns.clj:167)           
    clojure.core$map$fn__5587.invoke (core.clj:2747)                                                 
    clojure.lang.LazySeq.sval (LazySeq.java:40)                                                         
    clojure.lang.LazySeq.seq (LazySeq.java:56)                                                          
    clojure.lang.RT.seq (RT.java:528)      
,,,

Better processing error reporting

When the formatting functions in cljstyle encounter a form they can't process, the error is some arbitrary exception like NullPointerException or ClassCastException which bubbles up from deep within the code. This means that the end user doesn't get any feedback about what form caused the error - the granularity is only on the file level.

An improvement would be to have an intermediate layer in transform or somewhere equivalent that would catch these errors and re-throw a wrapped exception which contained information about the location in the file (using rewrite-clj's custom zipper) as well as a string with a tightly-scoped form that reproduces the error. Minimally, you could zip up to the enclosing top-level form and report that in the error.

Vim instructions don't seem to work

Error executing vim.schedule lua callback: ~.vim/plugged/conjure/lua/conjure/buffer.lua:88: Vim(function):E746: Function name does not match script file name: cljstyle#fix

Maybe this is related to Conjure, I'll dig in a bit and try to find a way to make it work but just wanted to flag this.

I added the following to ~/.config/nvim/after/ftplugin/clojure.vim as described in the integrations doc.

" Add to file for vim or neovim:
" ~/.vim/after/ftplugin/clojure.vim
" ~/.config/nvim/after/ftplugin/clojure.vim

" NOTE: typically you'd set these to use a formatter, but in this case it fails
" since cljstyle usually can't run on partial forms.
"setlocal equalprg=cljstyle\ pipe
"setlocal formatprg=cljstyle\ pipe

" This can also go in autoload/cljstyle.vim
function cljstyle#fix()
    let cwd = getcwd()
    let winsave = winsaveview()
    execute "cd" . expand('%:p:h')

    :%!cljstyle pipe

    execute "cd" . cwd
    call winrestview(winsave)
endfunction

" Example shortcut to fix the current file
nnoremap <leader>cs :call cljstyle#fix()<cr>

NPE thrown when styling a nested namespaced map

If you have a namespaced map using keyword aliasing inside of a namespaced map that also uses keyword aliasing, cljstyle will throw a NullPointerException.

As a minimal example, this will throw a NullPointerException when you try to run cljstyle fix on the file:

(ns test
  (:require
    [foo :as foo]))


(def problem
   #::foo{:config
          #::foo{:id 1}})

Stack trace:

java.lang.NullPointerException: null
 at java.util.concurrent.ConcurrentHashMap.get (ConcurrentHashMap.java:936)
    clojure.lang.Namespace.find (Namespace.java:188)
    clojure.core$find_ns.invokeStatic (core.clj:4096)
    clojure.core$the_ns.invokeStatic (core.clj:4126)
    clojure.core$ns_name.invokeStatic (core.clj:4130)
    clojure.core$ns_name.invoke (core.clj:4130)
    rewrite_clj.node.seq.NamespacedMapNode.sexpr (seq.clj:57)
    rewrite_clj.node.protocols$fn__271$fn__315$G__277__317.invoke (protocols.clj:9)
    rewrite_clj.node.protocols$fn__271$fn__315$G__276__320.invoke (protocols.clj:9)
    clojure.core$map$fn__5587.invoke (core.clj:2747)
    clojure.lang.LazySeq.sval (LazySeq.java:40)
    clojure.lang.LazySeq.seq (LazySeq.java:49)
    clojure.lang.Cons.next (Cons.java:39)
    clojure.lang.RT.boundedLength (RT.java:1785)
    clojure.lang.RestFn.applyTo (RestFn.java:130)
    clojure.core$apply.invokeStatic (core.clj:657)
    clojure.core$apply.invoke (core.clj:652)
    rewrite_clj.node.seq$map_node$fn__1794.invoke (seq.clj:110)
    rewrite_clj.node.seq.SeqNode.sexpr (seq.clj:16)
    rewrite_clj.node.protocols$fn__271$fn__315$G__277__317.invoke (protocols.clj:9)
    rewrite_clj.node.protocols$fn__271$fn__315$G__276__320.invoke (protocols.clj:9)
    clojure.core$map$fn__5587.invoke (core.clj:2745)
    clojure.lang.LazySeq.sval (LazySeq.java:40)
    clojure.lang.LazySeq.seq (LazySeq.java:49)
    clojure.lang.RT.seq (RT.java:528)
    clojure.lang.RT.countFrom (RT.java:643)
    clojure.lang.RT.count (RT.java:636)
    rewrite_clj.node.seq$assert_namespaced_map_children.invokeStatic (seq.clj:40)
    rewrite_clj.node.seq$assert_namespaced_map_children.invoke (seq.clj:37)
    rewrite_clj.node.seq$namespaced_map_node.invokeStatic (seq.clj:115)
    rewrite_clj.node.seq$namespaced_map_node.invoke (seq.clj:112)
    rewrite_clj.parser.core$fn__2076.invokeStatic (core.clj:123)
    rewrite_clj.parser.core/fn (core.clj:111)
    clojure.lang.MultiFn.invoke (MultiFn.java:229)
    rewrite_clj.reader$read_with_meta.invokeStatic (reader.clj:132)
    rewrite_clj.reader$read_with_meta.invoke (reader.clj:128)
    rewrite_clj.parser.core$parse_next.invokeStatic (core.clj:35)
    rewrite_clj.parser.core$parse_next.invoke (core.clj:33)
    rewrite_clj.parser.core$parse_delim$fn__2050.invoke (core.clj:43)
    rewrite_clj.reader$read_repeatedly$fn__1454.invoke (reader.clj:141)
    clojure.core$repeatedly$fn__6176.invoke (core.clj:5089)
    clojure.lang.LazySeq.sval (LazySeq.java:40)
    clojure.lang.LazySeq.seq (LazySeq.java:49)
    clojure.lang.RT.seq (RT.java:528)
    clojure.core$seq__5124.invokeStatic (core.clj:137)
    clojure.core$take_while$fn__5638.invoke (core.clj:2896)
    clojure.lang.LazySeq.sval (LazySeq.java:40)
    clojure.lang.LazySeq.seq (LazySeq.java:49)
    clojure.lang.Cons.next (Cons.java:39)
    clojure.lang.RT.next (RT.java:706)
    clojure.core$next__5108.invokeStatic (core.clj:64)
    clojure.core$dorun.invokeStatic (core.clj:3134)
    clojure.core$doall.invokeStatic (core.clj:3140)
    clojure.core$doall.invoke (core.clj:3140)
    rewrite_clj.reader$read_repeatedly.invokeStatic (reader.clj:143)
    rewrite_clj.reader$read_repeatedly.invoke (reader.clj:137)
    rewrite_clj.parser.core$parse_delim.invokeStatic (core.clj:44)
    rewrite_clj.parser.core$parse_delim.invoke (core.clj:39)
    rewrite_clj.parser.core$fn__2090.invokeStatic (core.clj:172)
    rewrite_clj.parser.core/fn (core.clj:170)
    clojure.lang.MultiFn.invoke (MultiFn.java:229)
    rewrite_clj.reader$read_with_meta.invokeStatic (reader.clj:132)
    rewrite_clj.reader$read_with_meta.invoke (reader.clj:128)
    rewrite_clj.parser.core$parse_next.invokeStatic (core.clj:35)
    rewrite_clj.parser.core$parse_next.invoke (core.clj:33)
    rewrite_clj.parser$parse.invokeStatic (parser.clj:13)
    rewrite_clj.parser$parse.invoke (parser.clj:10)
    rewrite_clj.parser$parse_all$fn__2099.invoke (parser.clj:18)
    clojure.core$repeatedly$fn__6176.invoke (core.clj:5089)
    clojure.lang.LazySeq.sval (LazySeq.java:40)
    clojure.lang.LazySeq.seq (LazySeq.java:49)
    clojure.lang.RT.seq (RT.java:528)
    clojure.core$seq__5124.invokeStatic (core.clj:137)
    clojure.core$take_while$fn__5638.invoke (core.clj:2896)
    clojure.lang.LazySeq.sval (LazySeq.java:40)
    clojure.lang.LazySeq.seq (LazySeq.java:49)
    clojure.lang.Cons.next (Cons.java:39)
    clojure.lang.RT.next (RT.java:706)
    clojure.core$next__5108.invokeStatic (core.clj:64)
    clojure.core$dorun.invokeStatic (core.clj:3134)
    clojure.core$doall.invokeStatic (core.clj:3140)
    clojure.core$doall.invoke (core.clj:3140)
    rewrite_clj.parser$parse_all.invokeStatic (parser.clj:20)
    rewrite_clj.parser$parse_all.invoke (parser.clj:15)
    rewrite_clj.parser$parse_string_all.invokeStatic (parser.clj:35)
    rewrite_clj.parser$parse_string_all.invoke (parser.clj:32)
    cljstyle.format.core$reformat_string.invokeStatic (core.clj:57)
    cljstyle.format.core$reformat_string.invoke (core.clj:51)
    cljstyle.format.core$reformat_file.invokeStatic (core.clj:68)
    cljstyle.format.core$reformat_file.invoke (core.clj:62)
    cljstyle.task.core$pipe.invokeStatic (core.clj:324)
    cljstyle.task.core$pipe.invoke (core.clj:319)
    cljstyle.tool.main$_main$fn__3428.invoke (main.clj:72)
    cljstyle.tool.main$_main.invokeStatic (main.clj:66)
    cljstyle.tool.main$_main.doInvoke (main.clj:37)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    cljstyle.tool.main.main (:-1)

Using version 0.12.0.

Allow indent-size to be overridden

Hi,

I notice that the default indent size (in indent.clj) is 2. There does not appear to be a way to change this (afaik). According to this guide, clojure style guide, 2 space indents are bad.

Whilst I'm not advocating changing the indent-size to 1, please can we have the ability to change the indent size to 1 if we choose to do so :-)

Thank you.

-=david=-

--overwrite not supported on macOS tar

Not sure what the ideal solution here is but Mac's tar command does not support the --overwrite option.

Workaround

h/t to @alexdao3 for this

When running ./install-cljstyle, you may get the following error on Mac:

tar: Option --overwrite is not supported"

If so, open the install-cljstyle file and remove the --overwrite option. Then re-run the ./install-cljstyle command.
This should install the cljstyle binary into your /usr/local/bin directory.

Want you to consider rename this library

Thank you for your great improvements.

I consider to use it as standard formatter for whole products in my company.
But the name of cljfmt cause some awkward situations such as following.

  • Make it difficult to find this repo from google search or Clojars
  • Name collision in the Vim ALE

Do you have the possibility of renaming it?

Bug: blank error message for a missing newline at end of file

Problem

If I accidentally edit a file so it has no newline, cljstyle check reports the problem like this:


1 files formatted incorrectly

That’s the whole output.

I was able to use cljstyle check --verbose to find the file the problem was in, parser.cljc, but the output still didn’t communicate the actual problem:

Using default cljstyle configuration for .
Source file src/cljsjs/marked.cljs is formatted correctly
[…]
Source file src/cljs/athens/core.cljs is formatted correctly
Source file src/cljc/athens/parser.cljc is formatted incorrectly

Source file src/cljs/athens/page.cljs is formatted correctly
[…]
{:files {:unrelated 9620, :correct 22, :incorrect 1}, :total 9643, :elapsed 544.152959}
Checked 9643 files in 544 ms
  9620 unrelated
    22 correct
     1 incorrect
1 files formatted incorrectly

I was only able to figure out this was a newline problem by running cljstyle fix and checking git diff.

I’m using cljstyle 0.12.1, installed with brew cask install cljstyle.

Expected result

Since cljstyle check normally reports differences as diffs, I would expect the output to be more like this:

--- a/src/cljc/athens/parser.cljc
+++ b/src/cljc/athens/parser.cljc
@@ -62,4 +62,4 @@
 (defn parse-to-ast
   "Converts a string of block syntax to an abstract syntax tree for Athens markup."
   [string]
-  (transform-to-ast (block-parser string)))
\ No newline at end of file
+  (transform-to-ast (block-parser string)))
1 files formatted incorrectly

Comment as first element breaks argument alignment

Comment as first element breaks argument alignment:

❯ cat cljstyle.clj
(defn req->user
[req]
  (or ;; Hello world
      (foo-1 req)
      (foo-2 req)
      (foo-3 req)))

~/tmp master*
❯ cljstyle check cljstyle.clj
--- a/cljstyle.clj
+++ b/cljstyle.clj
@@ -1,6 +1,6 @@
 (defn req->user
   [req]
-  (or ;; Hello world
-      (foo-1 req)
-      (foo-2 req)
-      (foo-3 req)))
+  (or ; Hello world
+   (foo-1 req)
+   (foo-2 req)
+   (foo-3 req)))
1 files formatted incorrectly

Without the comment element, check accepts the formatting.

If the comment form is between the other elements, alignment works.

The same happens inside :require forms, if :namespaces :break-libs? is false and the first element on require list is a comment.

cljstyle removes comments in (ns ...)

(ns hoge
  ;; require start
  (:require
   [java-time :as jt])
  ;; import start
  (:import
   (java.sql
    Timestamp)
   (java.time
    DateTimeException
    Instant
    ZonedDateTime)
   (java.time.temporal
    ChronoUnit)))

cljstyle removes ;; import start and ;; require start comment. Is this intended?

Add hook configuration for pre-commit

pre-commit is a framework for managing and maintaining multi-language pre-commit hooks. I've written a custom pre-commit hook configuration that works with my locally installed cljstyle, but it would be nice to have this configuration to this repository so pre-commit can be automatically run without requiring a local user install of cljstyle.

formatting for `:require` and `:import`

Hi,

Thanks for the utility. I was wondering if there was a way to consider adding the ability to apply the styling as discussed here:

Clojure Style

(ns foo.bar
  (:require
   [honeysql.core :as sql]
   [clojure.string :as str]))

Applying cljstyle at the moment displays this:

--- a/src/foo/bar.clj
+++ b/src/foo/bar.clj
@@ -1,8 +1,8 @@
 (ns foo.bar
   (:require
-   [honeysql.core :as sql]
-   [clojure.string :as str]))
+    [clojure.string :as str]
+    [honeysql.core :as sql]))

You'll notice that the require is 2 spaces in, but then the statements after are only 1 space in (this is also the same for imports).

I do understand that the Clojure Style guide is one person's work, but it does have traction within the community and whilst not everyone agrees 100% of what it says, there are parts that are nice - and this particular formatting of the imports/requires is nice :-)

Thank you for your time and consideration.

-=david=-

Namespace indent-size overrides

Using this as a reference the indentation for forms within ns are not customizable.

With this configuration:

{:rules
 {:namespaces
  {:indent-size 1}}}

it consistently formats to this style with 2 spaces instead of 1:

(ns format-test
  (:require
    [clojure.set :as set]
    [clojure.string :as str]))

Support "indent like" configuration

clj-kondo has a nice feature where you can configure it to lint a particular macro as if it were a different macro, so you don't have to write custom linters for macros if they're not necessary. That got me thinking—it would be really nice if cljstyle supported something like that in the :indents configuration, e.g.

;; .cljstyle
{:indents {def-special-fn defn}}

Would indent def-special-fn like defn. It might even be worth adding a new configuration key so you could opt into certain special formats, as the example above wouldn't format its arg vector liked defn.

NPE on namespaced map with relative key namespaced map value

$ cat foo.clj
#:foo{:foo #::bar{}}
$ cljfmt check -v foo.clj
,,,
java.lang.NullPointerException: null                                                                    
 at java.util.concurrent.ConcurrentHashMap.get (ConcurrentHashMap.java:936)                             
    clojure.lang.Namespace.find (Namespace.java:188)                                                    
    clojure.core$find_ns.invokeStatic (core.clj:4096)                                                   
    clojure.core$the_ns.invokeStatic (core.clj:4126)                                                    
    clojure.core$ns_name.invokeStatic (core.clj:4130)                                                   
    clojure.core$ns_name.invoke (core.clj:4130)                                                         
    rewrite_clj.node.seq.NamespacedMapNode.sexpr (seq.clj:57)                                           
    rewrite_clj.node.protocols$fn__271$fn__315$G__277__317.invoke (protocols.clj:9)
    rewrite_clj.node.protocols$fn__271$fn__315$G__276__320.invoke (protocols.clj:9)                     
    clojure.core$map$fn__5587.invoke (core.clj:2747)                                                    
    clojure.lang.LazySeq.sval (LazySeq.java:40)                                                         
    clojure.lang.LazySeq.seq (LazySeq.java:49)                                                          
    clojure.lang.Cons.next (Cons.java:39)                                                               
    clojure.lang.RT.boundedLength (RT.java:1785)                                                        
    clojure.lang.RestFn.applyTo (RestFn.java:130)                                                       
    clojure.core$apply.invokeStatic (core.clj:657)                                                      
    clojure.core$apply.invoke (core.clj:652)                                                            
    rewrite_clj.node.seq$map_node$fn__1794.invoke (seq.clj:110)                                         
    rewrite_clj.node.seq.SeqNode.sexpr (seq.clj:16)                                                     
    rewrite_clj.node.protocols$fn__271$fn__315$G__277__317.invoke (protocols.clj:9)                     
    rewrite_clj.node.protocols$fn__271$fn__315$G__276__320.invoke (protocols.clj:9)                     
,,,

Note that it only happens if the value namespaced map is a relative namespaced key. This file checks okay, for example:

$ cat foo.clj
#:foo{:foo #:bar{}}

import statements in ns are splited

cljstyle splits import statements:

(:import
  (java.text 
      SimpleDateFormat)
  (java.util
      Date))

but i would like to be:

(:import
   (java.text SimpleDateFormat)
   (java.util Date))

I tried with this config

{
 :rules {:blank-lines {:max-consecutive 1
                       :padding-lines 1}
         :indentation {:indents {#"^[^ \[]" [[:inner 0]]}}
         :namespaces {:import-break-width 200
                      :single-import-break-width 200}}
}

but it doesn't work
How could I obtain this behavior?

Performance improvements

After doing a bit of profiling with clj-async-profiler, I noticed that some of the new formatting rules consume more CPU than they should need to. Of a sample flame graph where reformat-form took 43.47% of the measured CPU time, that time was spent:

  • 21.25% cljstyle.format.type/reformat
  • 8.85% cljstyle.format.indent/reindent
  • 5.22%cljstyle.format.fn/line-break-functions
  • 4.20% cljstyle.format.var/line-break-vars
  • 3.95% other rules

This means that the type rules take almost half of the CPU time, despite type definitions being a small fraction of the actual code! It would be better to consider a more targeted approach, where the form is first searched for a relevant type form, then edits are applied to just that subform. The functions in rewrite-clj.zip.subedit might be useful for this.

Static Build?

I saw from the readme: "The native binaries are self-contained, so to install them simply place them on your path." which perked my attention, so I tried a binary on my NixOS box, but (as usual with NixOS) it fails to run with "no such file or directory", which means that it's dynamically linked (apparently it is only libz that I was missing on my platform, but for various reasons that's non trivial to get working in the system.

According to https://www.graalvm.org/reference-manual/native-image/StaticImages/ it should be possible to make a static image by passing in --static to the native-image call. I would make a PR to do that, but since I don't have a running version of graalvm I wouldn't be able to test it. Perhaps it's worth a try?

Exception when using `##Inf` or `##-Inf` as a default value in maps

A rewrite-clj bug: clj-commons/rewrite-clj#75

$ cat foo.clj
(:min m ##Inf)
$ cljstyle check -v foo.clj
Using cljstyle configuration from 1 sources for foo.clj:
<path redacted>/.cljstyle
Error while processing file foo.clj
java.lang.Exception: :reader-macro node expects 2 values. [at line 1, column 15]
 at rewrite_clj.reader$throw_reader.invokeStatic (reader.clj:18)
    rewrite_clj.reader$throw_reader.doInvoke (reader.clj:11)
    clojure.lang.RestFn.invoke (RestFn.java:490)
    rewrite_clj.reader$read_n.invokeStatic (reader.clj:157)
    rewrite_clj.reader$read_n.invoke (reader.clj:145)
    rewrite_clj.parser.core$parse_printables.invokeStatic (core.clj:50)
    rewrite_clj.parser.core$parse_printables.doInvoke (core.clj:46)
    clojure.lang.RestFn.invoke (RestFn.java:445)
    rewrite_clj.parser.core$fn__2072.invokeStatic (core.clj:142)
    rewrite_clj.parser.core/fn (core.clj:111)
    clojure.lang.MultiFn.invoke (MultiFn.java:229)
    rewrite_clj.reader$read_with_meta.invokeStatic (reader.clj:132)
    rewrite_clj.reader$read_with_meta.invoke (reader.clj:128)
    rewrite_clj.parser.core$parse_next.invokeStatic (core.clj:35)
    rewrite_clj.parser.core$parse_next.invoke (core.clj:33)
    rewrite_clj.reader$read_n.invokeStatic (reader.clj:153)
    rewrite_clj.reader$read_n.invoke (reader.clj:145)
    rewrite_clj.parser.core$parse_printables.invokeStatic (core.clj:50)
    rewrite_clj.parser.core$parse_printables.doInvoke (core.clj:46)
    clojure.lang.RestFn.invoke (RestFn.java:445)
    rewrite_clj.parser.core$fn__2072.invokeStatic (core.clj:142)
    rewrite_clj.parser.core/fn (core.clj:111)
    clojure.lang.MultiFn.invoke (MultiFn.java:229)
    rewrite_clj.reader$read_with_meta.invokeStatic (reader.clj:132)
    rewrite_clj.reader$read_with_meta.invoke (reader.clj:128)
    rewrite_clj.parser.core$parse_next.invokeStatic (core.clj:35)
    rewrite_clj.parser.core$parse_next.invoke (core.clj:33)
    rewrite_clj.parser.core$parse_delim$fn__2046.invoke (core.clj:43)
    rewrite_clj.reader$read_repeatedly$fn__1450.invoke (reader.clj:141)
    clojure.core$repeatedly$fn__6176.invoke (core.clj:5089)
    clojure.lang.LazySeq.sval (LazySeq.java:40)
    clojure.lang.LazySeq.seq (LazySeq.java:49)
    clojure.lang.RT.seq (RT.java:528)
    clojure.core$seq__5124.invokeStatic (core.clj:137)
    clojure.core$take_while$fn__5638.invoke (core.clj:2896)
    clojure.lang.LazySeq.sval (LazySeq.java:40)
    clojure.lang.LazySeq.seq (LazySeq.java:49)
    clojure.lang.Cons.next (Cons.java:39)
    clojure.lang.RT.next (RT.java:706)
    clojure.core$next__5108.invokeStatic (core.clj:64)
    clojure.core$dorun.invokeStatic (core.clj:3134)
    clojure.core$doall.invokeStatic (core.clj:3140)
    clojure.core$doall.invoke (core.clj:3140)
    rewrite_clj.reader$read_repeatedly.invokeStatic (reader.clj:143)
    rewrite_clj.reader$read_repeatedly.invoke (reader.clj:137)
    rewrite_clj.parser.core$parse_delim.invokeStatic (core.clj:44)
    rewrite_clj.parser.core$parse_delim.invoke (core.clj:39)
    rewrite_clj.parser.core$fn__2086.invokeStatic (core.clj:172)
    rewrite_clj.parser.core/fn (core.clj:170)
    clojure.lang.MultiFn.invoke (MultiFn.java:229)
    rewrite_clj.reader$read_with_meta.invokeStatic (reader.clj:132)
    rewrite_clj.reader$read_with_meta.invoke (reader.clj:128)
    rewrite_clj.parser.core$parse_next.invokeStatic (core.clj:35)
    rewrite_clj.parser.core$parse_next.invoke (core.clj:33)
    rewrite_clj.parser$parse.invokeStatic (parser.clj:13)
    rewrite_clj.parser$parse.invoke (parser.clj:10)
    rewrite_clj.parser$parse_all$fn__2095.invoke (parser.clj:18)
    clojure.core$repeatedly$fn__6176.invoke (core.clj:5089)
    clojure.lang.LazySeq.sval (LazySeq.java:40)
    clojure.lang.LazySeq.seq (LazySeq.java:49)
    clojure.lang.RT.seq (RT.java:528)
    clojure.core$seq__5124.invokeStatic (core.clj:137)
    clojure.core$take_while$fn__5638.invoke (core.clj:2896)
    clojure.lang.LazySeq.sval (LazySeq.java:40)
    clojure.lang.LazySeq.seq (LazySeq.java:49)
    clojure.lang.RT.seq (RT.java:528)
    clojure.core$seq__5124.invokeStatic (core.clj:137)
    clojure.core$dorun.invokeStatic (core.clj:3125)
    clojure.core$doall.invokeStatic (core.clj:3140)
    clojure.core$doall.invoke (core.clj:3140)
    rewrite_clj.parser$parse_all.invokeStatic (parser.clj:20)
    rewrite_clj.parser$parse_all.invoke (parser.clj:15)
    rewrite_clj.parser$parse_string_all.invokeStatic (parser.clj:35)
    rewrite_clj.parser$parse_string_all.invoke (parser.clj:32)
    cljstyle.format.core$reformat_string.invokeStatic (core.clj:57)
    cljstyle.format.core$reformat_string.invoke (core.clj:51)
    cljstyle.format.core$reformat_file.invokeStatic (core.clj:68)
    cljstyle.format.core$reformat_file.invoke (core.clj:62)
    cljstyle.task.core$check_source.invokeStatic (core.clj:239)
    cljstyle.task.core$check_source.invoke (core.clj:235)
    cljstyle.task.process$processing_action$compute_BANG___3013.invoke (process.clj:105)
    clojure.lang.AFn.applyToHelper (AFn.java:152)
    clojure.lang.AFn.applyTo (AFn.java:144)
    clojure.core$apply.invokeStatic (core.clj:657)
    clojure.core$with_bindings_STAR_.invokeStatic (core.clj:1965)
    clojure.core$with_bindings_STAR_.doInvoke (core.clj:1965)
    clojure.lang.RestFn.invoke (RestFn.java:425)
    clojure.lang.AFn.applyToHelper (AFn.java:156)
    clojure.lang.RestFn.applyTo (RestFn.java:132)
    clojure.core$apply.invokeStatic (core.clj:661)
    clojure.core$bound_fn_STAR_$fn__5471.doInvoke (core.clj:1995)
    clojure.lang.RestFn.invoke (RestFn.java:397)
    cljstyle.task.process$processing_action$fn__3017.invoke (process.clj:133)
    cljstyle.task.process.proxy$java.util.concurrent.RecursiveAction$ff19274a.compute (:-1)
    java.util.concurrent.RecursiveAction.exec (RecursiveAction.java:189)
    java.util.concurrent.ForkJoinTask.doExec (ForkJoinTask.java:289)
    java.util.concurrent.ForkJoinPool$WorkQueue.runTask (ForkJoinPool.java:1056)
    java.util.concurrent.ForkJoinPool.runWorker (ForkJoinPool.java:1692)
    java.util.concurrent.ForkJoinWorkerThread.run (ForkJoinWorkerThread.java:157)
    com.oracle.svm.core.thread.JavaThreads.threadStartRoutine (JavaThreads.java:479)
{:files {:process-error 1}, :total 1, :elapsed 7.742605}
Checked 1 files in 7.74 ms
     1 process-error
Failed to process 1 files

Cljfmt eats a comment in this import statement

$ cljfmt check foo.clj 
--- a/foo.clj
+++ b/foo.clj
@@ -2,7 +2,6 @@
   (:import
     ;; Single-class imports can be collapsed into a symbol
     clojure.lang.Keyword
-    ;; Otherwise break the class names out onto new lines
     (java.io
       InputStream
       OutputStream)))
1 files formatted incorrectly
$ cat foo.clj
(ns foo
  (:import
    ;; Single-class imports can be collapsed into a symbol
    clojure.lang.Keyword
    ;; Otherwise break the class names out onto new lines
    (java.io
      InputStream
      OutputStream)))

deps.edn usage fails with sha, ClassNotFoundException errors

Hi there,

When following the usage instructions with deps.edn I get two different classes of errors, even on an empty dir.

Using just :tag doesn't seem to be supported anymore:

filipesilva@Filipes-Air ~/s/empty-dir [1]> clj -Sdeps '{:deps {mvxcvi/cljstyle {:git/url "https://github.com/greglook/cljstyle.git", :tag "0.15.0"}}}' \
                                               -m cljstyle.main \
                                               check
Error building classpath. Library mvxcvi/cljstyle has coord with missing sha

If I use the sha I get a ClassNotFoundException instead:

filipesilva@Filipes-Air ~/s/empty-dir [1]> clj -Sdeps '{:deps {mvxcvi/cljstyle {:git/url "https://github.com/greglook/cljstyle.git", :sha "13fd2a3ff4097f1ed5c7ec96baf13e547ac2a26b"}}}' \
                                               -m cljstyle.main \
                                               check
WARNING: Implicit use of clojure.main with options is deprecated, use -M
Syntax error (ClassNotFoundException) compiling at (cljstyle/format/zloc.clj:1:1).
rewrite_clj.node.stringz.StringNode

Full report at:
/var/folders/hx/dl3d0pqn6615z78g83gc26300000gn/T/clojure-1489479610023568038.edn

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.