npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

parlinter

v1.3.0

Published

A minimal formatting linter for Lisp code (using Parinfer)

Readme

Parlinter

A low-friction linter for Lisp that finally allows your team members to use Parinfer.

Unlike full pretty-printers, it preserves as much of the original source as possible, only fixing confusing indentation and dangling close-parens. But it's still flexible, allowing any-space indentation within thresholds.

Adopt Parlinter to make your project Parinfer friendly!

parinfer friendly Build Status

Want a Quick Look?

Two Rules

Parlinter performs minimal source transformation in order to satisfy two rules:

Rule #1 - no dangling close-parens

Close-parens at the beginning of a line are moved to the end of its previous token:

;; bad
(foo (bar
   ) baz
 ) ^
 ^

;; fixed
(foo (bar)
    baz) ^
       ^

Conventional Formatters comply with Rule #1 for all cases except to allow close-parens after a comment. Parlinter does NOT make this exception.

Rule #2 - ordered indentation

Newlines must be indented to the right of their parent open-paren:

(foo (bar)
|
|
>
  bar)     ;; must be indented to the right of the point above: ✔️
  ^

But they cannot extend too far right into another open-paren:

(foo (bar)
      |
      |
      <
     bar)  ;; must be indented to the left of the point above: ✔️
     ^

To fix, indentation is clamped to these points:

;; bad
(foo (bar)
baz)

;; fixed
(foo (bar)
 baz)      ;; <-- nudged to the right
;; bad
(foo (bar)
      baz)

;; fixed
(foo (bar)
     baz)  ;; <-- nudged to the left

Conventional formatters comply with Rule #2 for all cases, except clojure.pprint and older versions of clojure-mode, which cause extra indentation of multi-arity function bodies.

Install

npm install -g parlinter
or
yarn global add parlinter

Want to use as a plugin in your build environment instead? (e.g. lein, boot) Help wanted! Please create an issue.

Usage

$ parlinter

Usage: parlinter [opts] [filename|glob ...]

Available options:
  --write                  Edit the file in-place. (Beware!)
  --trim                   Remove lines that become empty after linting.
  --list-different or -l   Print filenames of files that are different from Parlinter formatting.
  --stdin                  Read input from stdin.
  --version or -v          Print Parlinter version.

Glob patterns must be quoted.

Examples

Format all clojure files:

parlinter --trim --write "**/*.{clj,cljs,cljc,edn}"

Verify non-whitespace changes below for peace-of-mind: (AST not changed)

git diff -w

Check if all clojure files are properly formatted (non-zero exit code if not):

$ parlinter -l "**/*.{clj,cljs,cljc,edn}"

Performance

It takes ~0.5s to run against ~40k lines. (tested on the Clojure and ClojureScript project repos)

It was heavily optimized to allow Parinfer to run at 60hz on a ~3k line file while typing.

Compatibility

Syntactically compatible with Clojure, Racket, Scheme, and other Lisps that follow this syntax:

  • delimiters (, {, [
  • strings "
  • characters \
  • comments ;

Culturally compatible with standard Lisp styles*:

* some allow close-parens on their own line, but still allow them to be removed as Parlinter does

Common Lint Results in Clojure

A collection of common changes performed by Parlinter on Clojure code—the Lisp I am most familiar with.

1. Multi-arity function bodies

Sometimes function bodies for multi-arity functions are indented past the function params.

;; bad
(defn foo
  "I have two arities."
  ([x]
     (foo x 1))
  ([x y]
     (+ x y)))

;; fixed
(defn foo
  "I have two arities."
  ([x]
   (foo x 1))
  ([x y]
   (+ x y)))

2. Close-parens after comments

Since close-parens cannot be at the beginning of a line, they cannot come after comments.

;; bad
(-> 10
    (foo 20)
    (bar 30)
    ;; my comment
    )

;; fixed
(-> 10
    (foo 20)
    (bar 30))
    ;; my comment

3. Lines inside strings are not touched

Indentation of lines inside multi-line strings is significant, so it is not modified:

;; bad
(foo (bar
"Hello
world"))

;; fixed
(foo (bar
      "Hello
world"))    ;; <-- not nudged

4. Recessed function bodies

Function bodies are sometimes indented to its grandparent form rather than its parent:

;; bad
(foo bar (fn [a]
  (println a)))

;; fixed
(foo bar (fn [a]
          (println a))) ;; <-- nudged to be inside "(fn"

5. Recessed lines after JSON-style {

It is sometimes common to use JSON-style indentation in a top-level EDN config:

;; bad
:cljsbuild {
  :builds [...]
}

;; fixed
:cljsbuild {
            :builds [...]} ;; <-- nudged to be inside "{"

;; fine (but not automated)
:cljsbuild {:builds [...]}

;; fine (but not automated)
:cljsbuild
{:builds [...]}

6. Recessed lines after #_ and comment

Comment and ignore forms are commonly added retroactively without adjusting indentation:

;; bad
#_(defn foo []
  (bar baz))

;; fixed
#_(defn foo []
   (bar baz))
;; bad
(comment
(defn foo []
  (bar baz))
)

;; fixed
(comment
 (defn foo []
   (bar baz)))

7. Vertically-aligned comments

Linting may throw off the alignment of comments, due to paren movement:

;; bad
(let [foo 1  ; this is number one
      bar 2  ; this is number two
      ]
  (+ foo bar))

;; fixed
(let [foo 1  ; this is number one
      bar 2]  ; this is number two
  (+ foo bar))

Motivation

Though Parinfer was designed to lower the barrier for newcomers, it faced a problem of practicality by not allowing them to collaborate smoothly with people who didn't use it. This friction was not part of the intended experience.

Parlinter was designed as an answer to this problem, since there now seems to be a growing acceptance of linters and even full-formatters like Prettier, refmt, and gofmt from other language communities.

Thus, I hope that Parlinter at least spurs some thoughts on what is an acceptable amount of process around linting in Lisp, whether or not Parinfer is worth linting for, and how else we can help newcomers get into Lisp easier.

(It may also open the door for some exciting next-gen things I'm not yet ready to talk about.)

Written for Lisp with <3