This changelog is loose. Versions are not semantic, they are incremental. Splint is not meant to be infrastructure, so don't rely on it like infrastructure; it is a helpful development tool.
style/redundant-nested-call
: Prefer (+ 1 2 3 4)
to (+ 1 2 (+ 3 4))
.style/into-literal
ignores when in a threaded context.lint/def-fn
ignores when in a syntax-quoted context.lint/defmethod-names
: Require that defmethod
calls define a name for the function body. This helps improve stack traces. Suggested names are built from the dispatch value and cannot be trusted to be unique or usable, so while the rule is safe, it does not support autocorrect. Disabled by default.style/is-eq-order
relaxed expected input to accept any non-quoted list. (See #25.):autocorrect
.--interactive
has been added to the CLI options, making it usable. (oops)--interactive
now applies :autocorrect
so it doesn't need to be specified.Big feature: Safety and Autocorrection
Every rule has been marked as safe or unsafe. Safe rules don't generate false positives and any suggested alternatives can be used directly. Unsafe rules may generate false positives or their suggested alternatives may contain errors.
Rules that are safe may also perform autocorrection, which is tracked in defrule
with :autocorrect
. Rules may only perform autocorrection if they're safe.
The Rules Overview has been expanded as well.
edamame
1.4.27 supports the Clojure 1.12 array syntax: Integer/1
.@note
, @safety
, and @examples
. All existing # Examples
have been converted to @examples
, and the relevant rule docstrings have been updated.:autocorrect
to defrule
, :safe
to config schema.lint/redundant-str-call
ignores when used in threading macros. (See #20.)lint/redundant-call
ignores when used in threading macros. (See #21.)nil
when input is too short in patterns, which fixes subtle issues with lint/cond-else
.support-clojure-version?
only compare minor versions if major version numbers match, and likewise with incremental/minor version numbers.lint/redundant-str-call
: Don't call str
on input that's guaranteed to be a string: Prefer "foo"
to (str "foo")
, (str "foo" bar)
to (str (str "foo" bar))
, and (format "foo%s" bar)
to (str (format "foo%s" bar))
. (See clj-kondo#2323 for inspiration.)lint/duplicate-case-test
: Don't use the same case test constant more than once.lint/locking-object
: Prefer to lock on a symbol bound to (Object.)
.--only RULE
cli flag to run only specified rules or genres. Can be used multiple times. (#13)clojure.pprint
to fipp for pretty-printing code. Fast and easy to extend.Splint encountered an error: ""
which is unhelpful and shameful.default.edn
are now :links
, a vector of strings. This allows for listing multiple references.??
only 1 or 2 arguments, and if provided, that the predicate is a symbol.json
and json-pretty
now work with Babashka, by relying on Babashka's built-in chehire.core
instead of clojure.data.json
. This shouldn't result in any observable differences. I'd use cheshire.core
for both, but cheshire.core
is much bigger and more complicated than clojure.data.json
, and it's a pain in the ass imo.deftest
(including in new_rule.tmpl
) back to using defexpect
. Sean fixed the 3-arg issue when I raised it in https://github.com/clojure-expectations/clojure-test/issues/35, and it's nice to only import a single namespace instead of multiple.naming/conventional-aliases
docs.naming/record-name
now uses camel-snake-kebab
to check and convert the given record name to PascalCase.:method-value
style to style/new-object
to suggest Foo/new
instead of Foo.
.lint/dot-class-method
and lint/dot-obj-method
when lint/prefer-method-values
is enabled.runner
functions.(:config ctx)
to (:rules ctx)
as map of rule-name to rule map. Add (:rules-by-type ctx)
, a map of simple-type to vector of rule names. Change check-all-rules-of-type
to reduce over rule names and pull the rule map from ctx
.lint/duplicate-field-name
.global
. (See #11 and #12)lint/assoc-fn
when f
is a macro. Covered or
explicitly, no good generalized solution at the moment. (See #15.)--print-config
properly includes the genre of printed rules.style/is-eq-order
: Prefer (is (= 200 status))
over (is (= status 200))
when writing assertions.style/prefer-for-with-literals
: Prefer (for [item coll] {:a 1 :b item})
over (map #(hash-map :a 1 :b %) coll)
. (See #10.)-r
/--require
cli flag that can be used multiple times and require
top-level config option that takes a vector of strings. These are loaded with load-file
at run-time to allow for custom rules to be written and used. (See #8.) This is inherently unsafe, so don't run code you don't know.?*
or ?+
will immediately return the rest of the current input instead of accumulating it one-by-one.lint/warn-on-reflection
now checks that the file contains a proper ns
form before issuing a diagnostic.General performance increases in rules:
lint/body-unquote-splicing
lint/if-else-nil
lint/underscore-in-namespace
lint/warn-on-reflection
metrics/parameter-count
naming/conversion-function
naming/predicate
naming/record-name
naming/single-segment-namespace
style/def-fn
style/eq-zero
style/prefer-clj-string
style/prefer-condp
style/reduce-str
style/single-key-in
style/tostring
style/useless-do
Remove documentation about ?_
short form, as it's covered by the existing ?
and _
binding rules.
Expand ?foo
short-forms in patterns to their (? foo)
special form. Simplifies matching functions, makes the pattern DSL more consistent. Now ?|foo
will throw immediately instead of part-way through macroexpansion.
Updated pattern docs with a small example at the top.
Simplified ?|
matcher logic to use a set, as that's faster than creating multiple read-form
patterns in a let block and checking each one.
Obj/staticMethod
when given (. Obj (staticMethod))
in lint/dot-class-usage
.naming/conversion-functions
when there's no -
in the part before -to-
. (Will warn on f-to-g
, will not warn on expect-f-to-c
.)lint/assoc-fn
.lint/prefer-method-values
: Prefer (^[] String/toUpperCase "noah")
to (.toUpperCase "noah")
.lint/require-explicit-param-tags
: Prefer (^[File] File/mkdir (io/file \"a\"))
to (File/mkdir (io/file \"a\"))
. Prefer (^[String String] File/createTempFile \"abc\" \"b\")
to (^[_ _] File/createTempFile \"abc\" \"b\")
. Has :missing
, :wildcard
, and :both
styles, which check for lack of any :param-tags
, usage of _
in a :param-tags
, and both. Defaults to :wildcard
.lint/prefer-method-values
in performance/dot-equals
.new_rule.tmpl
to use deftest
. defexpect
is a thin wrapper and has the annoying "if given two non-expect entries, wrap in expect", which doesn't work when we use custom expect macros.re-find:
and string:
syntaxes for path :excludes
. re-find
uses clojure.core/re-find
, so the regex doesn't have to match the entire file path, just any portion. string
uses clojure.string/includes?
, so a fixed string anywhere in the file path.^[]
/:param-tags
feature.lint/underscore-in-namespace
: Prefer (ns foo-bar)
to (ns foo_bar)
.performance/dot-equals
: Prefer (.equals "foo" bar)
to (= "foo" bar)
. Only cares about string literals right now.performance/single-literal-merge
: Prefer (assoc m :a 1 :b 2)
to (merge m {:a 1 :b 2})
. Has :single
and :multiple
styles, either a single assoc
call or threaded multiple calls.performance/into-transducer
: Prefer (into [] (map f) coll)
to (into [] (map f coll))
.style/trivial-for
: Prefer (map f items)
over (for [item items] (f item))
.style/reduce-str
: Prefer (clojure.string/join coll)
over (reduce str coll)
.global
top-level .splint.edn
config that applies to all rules.:excludes
in both global
and rules-specific configs. Accepts a vector of java.nio.file.FileSystem/getPathMatcher globs or regexes. When in global
, matching files are removed from being processed at all. When in a specific rule, the rule is disabled before matching files are checked.min
, max
, and distinct?
to lint/redundant-call
.style/set-literal-as-fn
default to false
. It's not idiomatic and I don't know that it's any faster either.--auto-gen-config
, adds test.performance/assoc-many
should only trigger when there are more than 1 pair.The big feature here is adding support to run splint
without specifying paths. Now Splint will read the deps.edn
or project.clj
file in the current directory and check the paths in the base definition as well as :dev
and :test
aliases/profiles if no path argument is given. Splint still doesn't support specifying paths in .splint.edn
, nor does it allow for using paths from a project file as well as additional paths when called, but those are planned.
The second big change is moving from the old DSL to the new pangloss/pattern
inspired DSL. More flexible, more usable, overall better.
The third is adding performance
rules. These are off by default and are designed for those who want to pinch their cycles. Some affect common code (get-in
) and some are much more rare (satisfies
), but they're all designed to help you be mindful of slower paths.
spat.parser
to splint.parser
.spat.pattern
to splint.pattern
. RIP spat
, you treated me well for 9 months, but keeping spat
and splint
separate is no longer helpful.performance/assoc-many
: Prefer (-> m (assoc :k1 1) (assoc :k2 2))
over (assoc m :k1 1 :k2 2)
.performance/avoid-satisfies
: Do not use clojure.core/satisfies?
, full stop.performance/get-in-literals
: Prefer (-> m :k1 :k2 :k3)
over (get-in m [:k1 :k2 :k3])
.performance/get-keyword
: Prefer (:k m)
over (get m :k)
.style/redundant-regex-constructor
: Prefer #"abc"
over (re-pattern #"abc")
.->list
: concrete list building instead of apply . Useful anywhere a lazy-seq might be returned otherwise. seq/vec input: 40/43 us -> 28/15 usmapv*
: mapv
but short-circuits empty input and uses object-array
. Still unsure of this one. 36 us -> 36 usrun!*
: run!
but short-circuits empty input and uses .iterator
to perform the side-effects. Does not support reduced
. 7 us -> 950 nspmap*
: Avoids lazy-seq overhead and relies on Java's built-in Executors. 3.34 s -> 202 mswalk*
and postwalk*
: Primarily useful in replace
, but may prove useful otherwise. Only supports simple-type
defined types. 72 us -> 25 ussplint.config/read-project-file
returns a map of :clojure-version
and :paths
, taken from the project file (deps.edn
or project.clj
) in the current directory. If no file is found, :paths
is nil
and :clojure-version
is pulled from *clojure-version*
.:min-clojure-version
in defrule
, allowing for rules to specify the minimum version of clojure they require. Rules that are below the supported version are disabled at preparation time and can't be enabled during a run. Acceptable shape is a map of at least one of :major
, :minor
, and :incremental
.
test-helpers/with-temp-file
and test-helpers/print-to-file!
to test file contents.spat.parser/parse-string
and spat.parser/parse-string-all
into the test-helper namespace, and replace with parse-file
which accepts the file-obj
map.splint.config/slurp-edn
to read config files, parsed with edamame.:spat/lit
metadata to :splint/lit
. :spat/lit
still works for the time being, but no promises.splint.printer/print-results
now accepts only the results
object, which should additionally have :checked-files
and :total-time
.simple
, full,
and clj-kondo
now print the number of files checked as well: "Linting took 1ms, checked 3 files, 3 style warnings"
splint.replace/revert-splint-reader-macros
into splint.printer
where it belongs.symbol
to correctly print unprintable special characters by converting sexprs to strings and then converting those to symbols.simple-type
and drop-quote
to splint.utils
.style/prefer-clj-string
: Prefer clojure.string
functions over raw interop. Defaults to true
.--[no]summary
cli flag to print or not print the summary line.:filename
in Diagnostic
is now a java.io.File
object, not a string. This is propogated through everything. I suspect no one is using these so I think I could change the Diagnostic
as well, but maybe I'll wait a min.make-edamame-opts
now accepts both features
and ns-state
, and parse-string
and parse-string-all
take in features
instead of ns-state
.cljc
files twice, both clj
and cljs
, with their respective sides of the reader conditionals applied.lint/warn-on-reflection
only runs in clj
files.farolero
. Didn't provide any benefits over judicious try
/catch
use. :(matcher-combinators.core/Matcher
protocol to java.io.File
, making match?
work nicely with both strings and file objects.rules-by-type
from a map of simple-type -> map of rule name -> rule to simple-type -> vec of rule.@
, not splint/deref
, etc).lint/warn-on-reflection
: Require that (set! *warn-on-reflection* true)
is called after the ns
declaration at the start of every file. Defaults to false
.--config
. Add --print-config
. No timeline for removal of --config
(maybe never?).edn
/ edn-pretty
output: Print diagnostics as edn using clojure.core/prn
and clojure.pprint/pprint
.json
and json-pretty
keys are now sorted.metrics/parameter-count
: Function parameter vectors shouldn't have more than 4 positional parameters. Has :positional
and :include-rest
styles (only positional or include & args
rest params too?), and :count
configurable value to set maximum number of parameters allowed.-s
/ --silent
command line flag to print literally nothing when running Splint.json
output: Print diagnostics as json using clojure.data.json
.json-pretty
output: Same as json
but prettified with pprint
.:checked-files
.corpus
files to handle large-scale tests.dev/
.splint.runner/run-impl
to decomplect processing cli options and returning a status code from performing the actual config loading and rule building and running.check-all
to properly call the existing architecture instead of mock it, to accurately test the run-impl
flow.splint.runner
errors. Shows no signs of slowing down the app, so will investigate other areas for usage as well.defn
metadata when fn name exactly matches defn
or defn-
and second form is a symbol.--no-parallel
was producing a lazy seq, now consumes to actually check all files. Oops lol.nil
parent form instead of treating the whole file as a top-level vector of forms. Fixes naming/lisp-case
.attr-maps
to defn
metadata when parsing defn
forms.--parallel
and --output
, now those are added later (see #5).lint/if-not-do
, style/when-not-do
.metrics/fn-length
: Function bodies shouldn't be longer than 10 lines. Has :body
and :defn
styles, and :length
configurable value to set maximum length.test-helpers/expect-match
to assert on submatches, transition all existing check-X
functions to use it instead.defn
forms in postprocessing and attach as metadata instead of parsing in individual rules.style/multiple-arity-order
with :arglists
metadata.#(.someMethod %)
in lint/fn-wrapper
.and
and or
in style/prefer-condp
.io/resource
issue..class
files from jar
.-v
and --version
cli flags to print the current version.--config TYPE
cli flag to print the diff
, local
, or full
configuration.splint.rules.helpers.parse-defn
when trying to parse ill-formed function definitions.#(do [%1 %2])
in style/useless-do
, add docstring note about it.naming/single-segment-namespace
: Prefer (ns foo.bar)
to (ns foo)
.lint/prefer-require-over-use
: Prefer (:require [clojure.string ...])
to (:use clojure.string)
. Accepts different styles in the replacement form: :as
, :refer [...]
and :refer :all
.naming/conventional-aliases
: Prefer idiomatic aliases for core libraries ([clojure.string :as str]
to [clojure.string :as string]
).naming/lisp-case
: Prefer kebab-case over other cases for top-level definitions. Relies on camel-snake-kebab.style/multiple-arity-order
: Function definitions should have multiple arities sorted fewest arguments to most: (defn foo ([a] 1) ([a b] 2) ([a b & more] 3))
lint/fn-wrapper
introduced in v1.2.3.*warn-on-reflection*
to all rules and rule template.:spat/import-ns
metadata as way to track when a symbol has been imported.noahtheduke.spat.pattern/simple-type
for performance.volatile
instead of atom
for bindings in noahtheduke.spat.pattern
.keep
to reduce
to avoid seq and laziness manipulation.some->
where appropriate for short-circuiting.&&.
rest args and parsed lists in :on-match
handlers by attaching :noahtheduke.spat.pattern/rest
metadata to bound rest args.edamame
to v1.3.21 to handle #:: {:a 1}
auto-resolved namespaced maps with spaces between the colons and the map literal.lint/thread-macro-one-arg
supports :inline
and :avoid-collections
styles.:updated
field in configuration edn, show in rule docs.:guide-ref
for style/prefer-clj-math
.<hr>
between each rule's docs.lint/dorun-map
.deploy
justfile recipe.markdown
output: Same text as full
but with a fancy horizontal bar, header, and code blocks.:chosen-style
allows for rules to have configuration and different "styles". The first supported is lint/not-empty?
showing either seq
or not-empty
.ctx
is no longer an atom, but a plain map. The :diagnostics
entry is now the atom.splint.runner/check-form
returns the entire updated ctx
object instead of just the diagnostics. (I'm not entirely sure that's reasonable, but it's easily changed.)lint
to style
genre:
apply-str
apply-str-interpose
apply-str-reverse
assoc-assoc
conj-vector
eq-false
eq-nil
eq-true
eq-zero
filter-complement
filter-vec-filterv
first-first
first-next
let-do
mapcat-apply-apply
mapcat-concat-map
minus-one
minus-zero
multiply-by-one
multiply-by-zero
neg-checks
nested-addition
nested-multiply
next-first
next-next
not-eq
not-nil
not-some-pred
plus-one
plus-zero
pos-checks
tostring
update-in-assoc
useless-do
when-do
when-not-call
when-not-do
when-not-empty
when-not-not
ctx
as first argument to :on-match
functions to pass in config to rules. Update functions in splint.runner
as necessary.noahtheduke.spat.pattern
functions.:spat/lit
metadata to treat special symbols in pattern DSL as their literal values.clojure.core
, then in noahtheduke.splint.rules.helpers
.:var
to :binding
.style/def-fn
: Prefer (let [z f] (defn x [y] (z y)))
over (def x (let [z f] (fn [y] (z y))))
lint/try-splicing
: Prefer (try (do ~@body) (finally ...))
over (try ~@body (finally ...))
.lint/body-unquote-splicing
: Prefer (binding [max mymax] (let [res# (do ~@body)] res#))
over (binding [max mymax] ~@body)
.--parallel
and --no-parallel
for running splint in parallel or not. Defaults to true
.:uneval
config option for :splint/disable
.build.clj
to resources/SPLINT_VERSION
.naming/record-name
: Add :message
.style/prefer-condp
: Only runs if given more than 1 predicate branch.style/set-literal-as-fn
: Allow quoted symbols in sets.Actually wrote out something of a changelog.
lint/duplicate-field-name
: (defrecord Foo [a b a])
naming/conversion-functions
: Should use x->y
instead of x-to-y
.style/set-literal-as-fn
: Should use (case elem (a b) true false)
instead of (#{'a 'b} elem)
:new-rule
task now creates a test stub in the correct test directory.#_:splint/disable
is treated as metadata on the following form and disables all rules. #_{:splint/disable []}
can take genres of rules ([lint]
) and/or specific rules ([lint/loop-do]
) and disables those rules. See below (Thoughts and Followup) for discussion and Configuration for more details.defrule
now requires the provided rule-name to be fully qualified, and doesn't perform any *ns*
magic to derive the genre.:init-type
in defrule
to handle symbol matching.:dispatch
reader macros provided by Edamame now wrap their sexps in the appropriate (splint/X sexp)
form, to distinguish them from the symbol forms. Aka #(inc %)
is now rendered as (splint/fn [%1] (inc %1))
, vs the original (fn* ...)
, or #'x
is now (splint/var x)
vs (var x)
. This allows for writing rules targeting the literal form instead of the symbol form, and requires that rule patterns rely on functions in noahtheduke.splint.rules.helpers
to cover these alternates.noahtheduke.splint.rules.helpers
as an autoresolving namespace so rules can use predicates defined within it without importing or qualifying.violation
to diagnostic
.lint/duplicate-field-name
wasn't checking that ?fields
was a vector before calling count
on it.I want another parser because I want access to comments. Without comments, I can't parse magic comments, meaning I can't enable or disable rules inline, only globally. That's annoying and not ideal. However, every solution I've dreamed up has some deep issue.
Edamame is our current parser and it's extremely fast (40ms to parse clojure/core.clj
) but it drops comments. I've forked it to try to add them, but that would mean handling them in every other part of the parser, such as syntax-quote and maps and sets, making dealing with those objects really hard. :sob:
Rewrite-clj only exposes comments in the zip api, meaning I have to operate on the zipper objects with zipper functions (horrible and slow). It's nice to rely on Clojure built-ins instead of (loop [zloc zloc] (z/next* ...))
nonsense.
clj-kondo is faster than rewrite-clj and has a nicer api, but the resulting tree isn't as easy to work with as Edamame and it's slower. Originally built Spat in it and found it to be annoying to use.
parcera looked promising, but the pre-processing in parcera/ast
is slow and operating on the Java directly is deeply cumbersome. The included grammar also makes some odd choices and I don't know ANTLR4 well enough to know how to fix them (such as including the :
in keyword strings). Additionally, if I were to switch, I would have to update/touch every existing rule.
After tinkering with Edamame for a bit, I've found a solution that requires no changes to edamame to support: #_:splint/disable
. This style of directive applies metadata to the following form: #_{:splint/disable [lint/plus-one]} (+ 1 x)
. Edamame normally discard #_
/discarded forms, so on Borkdude's recommendation, I use str/replace
to convert it at parse-time to metadata. This uses an existing convention and handles the issue of disabling multiple items or disabling for only a certain portion of the file.
Update readme with some better writing.
dev/sorted-rules-require
for internal use only:no-doc
.lint/cond-else
to style/cond-else
.Renamed to Splint! Things are really coming together now.
lint/assoc-in-one-arg
lint/update-in-one-arg
naming/predicate
naming/record-name
style/let-if
style/let-when
style/new-object
style/prefer-boolean
style/prefer-condp
style/redundant-let
style/single-key-in
-M:gen-docs
for rule documentation generation and formatting.-M:new-rule
task to generate a new rule file from a template.-M:deploy
task to push to clojars.lint/with-meta-vary-meta
to style/prefer-vary-meta
.lint/thread-macro-no-arg
to lint/redundant-call
.Initial release of spat
, announcement on Clojurian Slack and bbin installation set up. Contains working pattern matching system, a bunch of rules, and a simple runner.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close