├── resources ├── .keep ├── noahtheduke │ └── splint │ │ └── SPLINT_VERSION └── clj-kondo.exports │ └── io.github.noahtheduke │ └── splint │ └── config.edn ├── .cljfmt.edn ├── .clj-kondo ├── imports │ ├── babashka │ │ └── fs │ │ │ └── config.edn │ ├── nubank │ │ └── matcher-combinators │ │ │ └── config.edn │ ├── rewrite-clj │ │ └── rewrite-clj │ │ │ └── config.edn │ ├── cond_plus │ │ └── cond_plus │ │ │ └── config.edn │ └── io.github.noahtheduke │ │ └── splint │ │ └── config.edn └── config.edn ├── .editorconfig ├── .lsp └── config.edn ├── .markdownlint.json ├── tasks ├── new_rule_config.tmpl ├── new_rule_test.tmpl └── new_rule.tmpl ├── scripts ├── gen_toc.clj └── compile ├── corpus ├── parse_error.clj ├── only_test.clj ├── throw_in_middle.clj ├── printer_test.clj ├── nested_calls.clj ├── special_characters.clj └── arglists.clj ├── .gitignore ├── .projections.json ├── test └── noahtheduke │ └── splint │ ├── integrations │ └── helpers.clj │ ├── rules │ ├── style │ │ ├── when_not_not_test.clj │ │ ├── when_not_empty_test.clj │ │ ├── next_next_test.clj │ │ ├── first_next_test.clj │ │ ├── next_first_test.clj │ │ ├── not_eq_test.clj │ │ ├── first_first_test.clj │ │ ├── when_not_call_test.clj │ │ ├── conj_vector_test.clj │ │ ├── update_in_assoc_test.clj │ │ ├── apply_str_reverse_test.clj │ │ ├── mapcat_apply_apply_test.clj │ │ ├── apply_str_interpose_test.clj │ │ ├── nested_addition_test.clj │ │ ├── nested_multiply_test.clj │ │ ├── redundant_let_test.clj │ │ ├── when_not_do_test.clj │ │ ├── not_nil_test.clj │ │ ├── filter_vec_filterv_test.clj │ │ ├── minus_zero_test.clj │ │ ├── minus_one_test.clj │ │ ├── prefer_vary_meta_test.clj │ │ ├── multiply_by_one_test.clj │ │ ├── multiply_by_zero_test.clj │ │ ├── plus_zero_test.clj │ │ ├── eq_nil_test.clj │ │ ├── let_do_test.clj │ │ ├── plus_one_test.clj │ │ ├── eq_true_test.clj │ │ ├── pos_checks_test.clj │ │ ├── eq_false_test.clj │ │ ├── neg_checks_test.clj │ │ ├── useless_do_test.clj │ │ ├── trivial_for_test.clj │ │ ├── tostring_test.clj │ │ ├── mapcat_concat_map_test.clj │ │ ├── apply_str_test.clj │ │ ├── prefer_boolean_test.clj │ │ ├── not_some_pred_test.clj │ │ ├── prefer_clj_math_test.clj │ │ ├── when_do_test.clj │ │ ├── redundant_regex_constructor_test.clj │ │ ├── reduce_str_test.clj │ │ ├── eq_zero_test.clj │ │ ├── filter_complement_test.clj │ │ ├── prefixed_libspecs_test.clj │ │ ├── multiple_arity_order_test.clj │ │ ├── assoc_assoc_test.clj │ │ ├── set_literal_as_fn_test.clj │ │ ├── prefer_condp_test.clj │ │ ├── def_fn_test.clj │ │ └── single_key_in_test.clj │ ├── lint │ │ ├── divide_by_one_test.clj │ │ ├── loop_do_test.clj │ │ ├── dorun_map_test.clj │ │ ├── if_not_both_test.clj │ │ ├── if_not_not_test.clj │ │ ├── if_nil_else_test.clj │ │ ├── duplicate_field_name_test.clj │ │ ├── take_repeatedly_test.clj │ │ ├── try_splicing_test.clj │ │ ├── if_same_truthy_test.clj │ │ ├── incorrectly_swapped_test.clj │ │ ├── if_not_do_test.clj │ │ ├── loop_empty_when_test.clj │ │ ├── not_empty_test.clj │ │ ├── if_let_else_nil_test.clj │ │ ├── let_if_test.clj │ │ ├── locking_object_test.clj │ │ ├── missing_body_in_when_test.clj │ │ ├── dot_obj_method_test.clj │ │ ├── fn_wrapper_test.clj │ │ ├── no_target_for_method_test.clj │ │ ├── warn_on_reflection_test.clj │ │ ├── let_when_test.clj │ │ ├── if_else_nil_test.clj │ │ ├── rand_int_one_test.clj │ │ ├── prefer_method_values_test.clj │ │ ├── prefer_require_over_use_test.clj │ │ ├── min_max_test.clj │ │ └── no_op_assignment_test.clj │ ├── performance │ │ ├── avoid_satisfies_test.clj │ │ ├── get_keyword_test.clj │ │ └── get_in_literals_test.clj │ └── naming │ │ ├── single_segment_namespace_test.clj │ │ ├── conversion_functions_test.clj │ │ ├── predicate_test.clj │ │ └── record_name_test.clj │ └── utils_test.clj ├── src └── noahtheduke │ └── splint │ ├── rules │ ├── style │ │ ├── minus_zero.clj │ │ ├── plus_zero.clj │ │ ├── not_nil.clj │ │ ├── not_eq.clj │ │ ├── minus_one.clj │ │ ├── tostring.clj │ │ ├── multiply_by_one.clj │ │ ├── multiply_by_zero.clj │ │ ├── next_next.clj │ │ ├── eq_nil.clj │ │ ├── first_next.clj │ │ ├── conj_vector.clj │ │ ├── first_first.clj │ │ ├── next_first.clj │ │ ├── eq_true.clj │ │ ├── neg_checks.clj │ │ ├── pos_checks.clj │ │ ├── when_not_not.clj │ │ ├── eq_false.clj │ │ ├── plus_one.clj │ │ ├── when_not_call.clj │ │ ├── not_some_pred.clj │ │ ├── when_do.clj │ │ ├── let_do.clj │ │ ├── mapcat_apply_apply.clj │ │ ├── filter_vec_filterv.clj │ │ ├── apply_str_reverse.clj │ │ ├── mapcat_concat_map.clj │ │ ├── when_not_do.clj │ │ ├── apply_str_interpose.clj │ │ ├── trivial_for.clj │ │ ├── eq_zero.clj │ │ ├── nested_addition.clj │ │ ├── nested_multiply.clj │ │ ├── prefer_boolean.clj │ │ ├── update_in_assoc.clj │ │ ├── prefer_vary_meta.clj │ │ ├── when_not_empty.clj │ │ ├── apply_str.clj │ │ ├── filter_complement.clj │ │ ├── cond_else.clj │ │ ├── assoc_assoc.clj │ │ ├── redundant_let.clj │ │ ├── reduce_str.clj │ │ ├── single_key_in.clj │ │ ├── useless_do.clj │ │ ├── is_eq_order.clj │ │ ├── redundant_regex_constructor.clj │ │ ├── redundant_nested_call.clj │ │ └── multiple_arity_order.clj │ ├── lint │ │ ├── divide_by_one.clj │ │ ├── if_same_truthy.clj │ │ ├── if_not_both.clj │ │ ├── if_not_not.clj │ │ ├── loop_do.clj │ │ ├── if_nil_else.clj │ │ ├── if_not_do.clj │ │ ├── take_repeatedly.clj │ │ ├── if_let_else_nil.clj │ │ ├── missing_body_in_when.clj │ │ ├── loop_empty_when.clj │ │ ├── let_if.clj │ │ ├── let_when.clj │ │ ├── try_splicing.clj │ │ ├── duplicate_field_name.clj │ │ ├── dorun_map.clj │ │ ├── rand_int_one.clj │ │ ├── locking_object.clj │ │ ├── underscore_in_namespace.clj │ │ ├── warn_on_reflection.clj │ │ ├── assoc_fn.clj │ │ ├── if_else_nil.clj │ │ ├── min_max.clj │ │ ├── not_empty.clj │ │ ├── no_op_assignment.clj │ │ └── no_target_for_method.clj │ ├── performance │ │ ├── avoid_satisfies.clj │ │ ├── get_in_literals.clj │ │ └── get_keyword.clj │ └── naming │ │ ├── single_segment_namespace.clj │ │ └── record_name.clj │ ├── path_matcher.clj │ └── diagnostic.clj ├── dev ├── noahtheduke │ └── splint │ │ ├── rules │ │ └── dev │ │ │ └── throws_on_match.clj │ │ └── dev.clj └── user.clj ├── .splint.edn ├── docs ├── dev │ └── todo.md ├── cljdoc.edn └── installation.md └── bb.edn /resources/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/noahtheduke/splint/SPLINT_VERSION: -------------------------------------------------------------------------------- 1 | 1.22.0 2 | -------------------------------------------------------------------------------- /.cljfmt.edn: -------------------------------------------------------------------------------- 1 | {:indents ^:replace {#re ".*" [[:inner 0]]}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/imports/babashka/fs/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {babashka.fs/with-temp-dir clojure.core/let}} 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | max_line_length = off 8 | -------------------------------------------------------------------------------- /.lsp/config.edn: -------------------------------------------------------------------------------- 1 | {:cljfmt-config-path ".cljfmt.edn" 2 | :project-specs [{:project-path "deps.edn" 3 | :classpath-cmd ["clojure" "-A:dev:test" "-Spath"]}]} 4 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "blanks-around-headers": { "lines_below": 0 }, 4 | "line_length": false, 5 | "no-duplicate-header": { "siblings_only": true } 6 | } 7 | -------------------------------------------------------------------------------- /tasks/new_rule_config.tmpl: -------------------------------------------------------------------------------- 1 | {;; Please keep this sorted and split by genre 2 | 3 | {{genre}}/{{rule-name}} 4 | {:description "" 5 | :enabled true 6 | :added "<>" 7 | :updated "<>"} 8 | -------------------------------------------------------------------------------- /.clj-kondo/imports/nubank/matcher-combinators/config.edn: -------------------------------------------------------------------------------- 1 | {:linters 2 | {:unresolved-symbol 3 | {:exclude [(cljs.test/is [match? thrown-match?]) 4 | (clojure.test/is [match? thrown-match?])]}}} 5 | -------------------------------------------------------------------------------- /scripts/gen_toc.clj: -------------------------------------------------------------------------------- 1 | (ns gen-toc 2 | (:require 3 | [babashka.fs :as fs] 4 | [babashka.process :refer [shell]])) 5 | 6 | (doseq [f (fs/glob "docs/rules" "**.md")] 7 | (shell "markdown-toc -i --maxdepth 2" (str f))) 8 | -------------------------------------------------------------------------------- /corpus/parse_error.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | {:a 1 :a 1} 6 | -------------------------------------------------------------------------------- /.clj-kondo/imports/rewrite-clj/rewrite-clj/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as 2 | {rewrite-clj.zip/subedit-> clojure.core/-> 3 | rewrite-clj.zip/subedit->> clojure.core/->> 4 | rewrite-clj.zip/edit-> clojure.core/-> 5 | rewrite-clj.zip/edit->> clojure.core/->>}} 6 | -------------------------------------------------------------------------------- /corpus/only_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns only-test) 6 | 7 | (do (+ 1 foo)) 8 | 9 | (= "hello" bar) 10 | -------------------------------------------------------------------------------- /.clj-kondo/imports/cond_plus/cond_plus/config.edn: -------------------------------------------------------------------------------- 1 | {:linters {:cond-plus/empty-else {:level :error} 2 | :cond-plus/missing-fn {:level :error} 3 | :cond-plus/non-final-else {:level :error} 4 | :cond-plus/sequence {:level :error}} 5 | :hooks {:analyze-call {cond-plus.core/cond+ hooks.cond-plus-hook/cond+}}} 6 | -------------------------------------------------------------------------------- /corpus/throw_in_middle.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns throw-in-middle) 6 | 7 | (very-special-symbol :do-not-match) 8 | 9 | (let [a 1] (if a (+ a a) 2)) 10 | -------------------------------------------------------------------------------- /corpus/printer_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns printer-test) 6 | 7 | (when 8 | (not 9 | (= 1 1)) 10 | (do 11 | (prn 2) 12 | (prn 3))) 13 | 14 | (very-special-symbol :do-not-match) 15 | -------------------------------------------------------------------------------- /corpus/nested_calls.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | #_:splint/disable 6 | (ns nested-calls) 7 | 8 | (set! *warn-on-reflection* true) 9 | 10 | (+ 1 2 (+ 3 4)) 11 | 12 | (str (some-call) (str (another-call))) 13 | 14 | (str (str (another-call))) 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .calva/output-window/ 2 | .classpath 3 | .clj-kondo/.cache 4 | .cpcache 5 | .eastwood 6 | .factorypath 7 | .hg/ 8 | .hgignore 9 | .java-version 10 | .lein-* 11 | .lsp/.cache 12 | .lsp/sqlite.db 13 | .nrepl-history 14 | .nrepl-port 15 | .project 16 | .rebel_readline_history 17 | .settings 18 | .socket-repl-port 19 | .sw* 20 | .vscode 21 | *.class 22 | *.jar 23 | *.swp 24 | *~ 25 | /checkouts 26 | /classes 27 | /target 28 | tests-user.edn 29 | -------------------------------------------------------------------------------- /corpus/special_characters.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | @a 6 | 7 | @(a) 8 | 9 | #(+ 1 %) 10 | 11 | #=(+ 1 2) 12 | 13 | #"a" 14 | 15 | #'a 16 | 17 | #'(a b) 18 | 19 | `a 20 | 21 | `(a b) 22 | 23 | ~a 24 | 25 | ~(a b) 26 | 27 | ~@a 28 | 29 | ~@(a b) 30 | -------------------------------------------------------------------------------- /.projections.json: -------------------------------------------------------------------------------- 1 | { 2 | "doc/*.md": { "type": "doc" }, 3 | "README.md": { "type": "doc" }, 4 | "src/noahtheduke/*.clj": { 5 | "type": "source", 6 | "alternate": "test/noahtheduke/{}_test.clj" 7 | }, 8 | "src/noahtheduke/*.cljc": { 9 | "type": "source", 10 | "alternate": "test/noahtheduke/{}_test.cljc" 11 | }, 12 | "test/noahtheduke/*_test.clj": { 13 | "type": "test", 14 | "alternate": "src/noahtheduke/{}.clj" 15 | }, 16 | "test/noahtheduke/*_test.cljc": { 17 | "type": "test", 18 | "alternate": "src/noahtheduke/{}.cljc" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | {:config-paths ["../resources/clj-kondo.exports/io.github.noahtheduke/splint"] 6 | :config-in-call {noahtheduke.splint.rules/defrule {:linters {:unused-binding {:level :off}}}} 7 | :config-in-comment {:linters {:unused-value {:level :off}}} 8 | :linters {:clojure-lsp/unused-public-var {:level :off} 9 | :unresolved-namespace {:exclude [user]}} 10 | :cljc {:features #{:clj}} 11 | :output {:linter-name true}} 12 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/integrations/helpers.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.integrations.helpers 6 | (:require 7 | [noahtheduke.splint.clojure-ext.core :refer [update-vals*]] 8 | [noahtheduke.splint.config :refer [read-default-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defn usefully-enabled-config [] 13 | (-> (read-default-config) 14 | (update-vals* #(assoc % :enabled true)) 15 | (assoc-in ['style/set-literal-as-fn :enabled] false))) 16 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/minus_zero.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.minus-zero 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/minus-zero 12 | "Checks for x - 0. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (- x 0) 18 | 19 | ; prefer 20 | x 21 | " 22 | {:pattern '(- ?x 0) 23 | :message "Subtracting 0 is a no-op." 24 | :autocorrect true 25 | :replace '?x}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/divide_by_one.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.divide-by-one 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/divide-by-one 12 | "Checks for `(/ x 1)`. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (/ x 1) 18 | 19 | ; prefer 20 | x 21 | " 22 | {:pattern '(/ ?x 1) 23 | :message "Dividing by 1 is a no-op." 24 | :autocorrect true 25 | :replace '?x}) 26 | -------------------------------------------------------------------------------- /dev/noahtheduke/splint/rules/dev/throws_on_match.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.dev.throws-on-match 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule dev/throws-on-match 12 | "Rules in `noahtheduke.splint` must be in sorted order." 13 | {:pattern '(very-special-symbol :do-not-match) 14 | :message "Deliberate throws if matched" 15 | :on-match (fn [ctx rule form _bindings] 16 | (throw (ex-info "matched" {:extra :data})))}) 17 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/plus_zero.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.plus-zero 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/plus-zero 12 | "Checks for x + 0. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (+ x 0) 18 | (+ 0 x) 19 | 20 | ; prefer 21 | x 22 | " 23 | {:patterns ['(+ ?x 0) 24 | '(+ 0 ?x)] 25 | :message "Adding 0 is a no-op." 26 | :autocorrect true 27 | :replace '?x}) 28 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/not_nil.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.not-nil 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/not-nil? 12 | "`some?` exists so use it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (not (nil? x)) 18 | 19 | ; prefer 20 | (some? x) 21 | " 22 | {:pattern '(not (nil? ?x)) 23 | :message "Use `some?` instead of recreating it." 24 | :autocorrect true 25 | :replace '(some? ?x)}) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/when_not_not_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.when-not-not-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/when-not-not) 13 | 14 | (defdescribe when-not-not-test 15 | (it "works" 16 | (expect-match 17 | [{:alt '(when x y)}] 18 | "(when-not (not x) y)" 19 | (single-rule-config rule-name)))) 20 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/if_same_truthy.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.if-same-truthy 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/if-same-truthy 12 | "`or` exists so use it lol. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (if x x y) 18 | 19 | ; prefer 20 | (or x y) 21 | " 22 | {:pattern '(if ?x ?x ?y) 23 | :message "Use `or` instead of recreating it." 24 | :autocorrect true 25 | :replace '(or ?x ?y)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/not_eq.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.not-eq 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/not-eq 12 | "`not=` exists, so use it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (not (= num1 num2)) 18 | 19 | ; prefer 20 | (not= num1 num2) 21 | " 22 | {:pattern '(not (= ?+args)) 23 | :message "Use `not=` instead of recreating it." 24 | :autocorrect true 25 | :replace '(not= ?args)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/minus_one.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.minus-one 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/minus-one 12 | "Checks for simple -1 that should use `clojure.core/dec`. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (- x 1) 18 | 19 | ; prefer 20 | (dec x) 21 | " 22 | {:pattern '(- ?x 1) 23 | :message "Use `dec` instead of recreating it." 24 | :autocorrect true 25 | :replace '(dec ?x)}) 26 | -------------------------------------------------------------------------------- /.clj-kondo/imports/io.github.noahtheduke/splint/config.edn: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | {:hooks {:analyze-call {noahtheduke.splint.rules/defrule 6 | hooks.noahtheduke.splint.rules/defrule}} 7 | :lint-as {noahtheduke.splint.test-helpers/with-temp-files clojure.core/let} 8 | :linters {:splint/arg-count {:level :error} 9 | :splint/binding-type {:level :error} 10 | :splint/choice {:level :error} 11 | :splint/choice-type {:level :error} 12 | :splint/predicate {:level :error} 13 | :splint/spec {:level :error}}} 14 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/tostring.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.tostring 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/tostring 12 | "Convert `(.toString)` to `(str)`. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (.toString x) 18 | 19 | ; prefer 20 | (str x) 21 | " 22 | {:pattern '((?| _ [.toString String/toString]) ?x) 23 | :message "Use `str` instead of interop." 24 | :autocorrect true 25 | :replace '(str ?x)}) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/when_not_empty_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.when-not-empty-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/when-not-empty?) 13 | 14 | (defdescribe when-not-empty?-test 15 | (it "works" 16 | (expect-match 17 | [{:alt '(when (seq x) y)}] 18 | "(when-not (empty? x) y)" 19 | (single-rule-config rule-name)))) 20 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/multiply_by_one.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.multiply-by-one 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/multiply-by-one 12 | "Checks for (* x 1). 13 | 14 | @examples 15 | 16 | ; avoid 17 | (* x 1) 18 | (* 1 x) 19 | 20 | ; prefer 21 | x 22 | " 23 | {:patterns ['(* ?x 1) 24 | '(* 1 ?x)] 25 | :message "Multiplying by 1 is a no-op." 26 | :autocorrect true 27 | :replace '?x}) 28 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/multiply_by_zero.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.multiply-by-zero 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/multiply-by-zero 12 | "Checks for (* x 0). 13 | 14 | @examples 15 | 16 | ; avoid 17 | (* x 0) 18 | (* 0 x) 19 | 20 | ; prefer 21 | 0 22 | " 23 | {:patterns ['(* ?x 0) 24 | '(* 0 ?x)] 25 | :message "Multiplying by 0 is a no-op." 26 | :autocorrect true 27 | :replace '0}) 28 | -------------------------------------------------------------------------------- /resources/clj-kondo.exports/io.github.noahtheduke/splint/config.edn: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | {:hooks {:analyze-call {noahtheduke.splint.rules/defrule 6 | hooks.noahtheduke.splint.rules/defrule}} 7 | :lint-as {noahtheduke.splint.test-helpers/with-temp-files clojure.core/let} 8 | :linters {:splint/arg-count {:level :error} 9 | :splint/binding-type {:level :error} 10 | :splint/choice {:level :error} 11 | :splint/choice-type {:level :error} 12 | :splint/predicate {:level :error} 13 | :splint/spec {:level :error}}} 14 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/if_not_both.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.if-not-both 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/if-not-both 12 | "`if-not` exists, so use it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (if (not x) y z) 18 | 19 | ; prefer 20 | (if-not x y z) 21 | " 22 | {:pattern '(if (not ?x) ?y ?z) 23 | :message "Use `if-not` instead of recreating it." 24 | :autocorrect true 25 | :replace '(if-not ?x ?y ?z)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/if_not_not.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.if-not-not 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/if-not-not 12 | "Two `not`s cancel each other out. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (if-not (not x) y z) 18 | 19 | ; prefer 20 | (if x y z) 21 | " 22 | {:pattern '(if-not (not ?x) ?y ?z) 23 | :message "Use `if` instead of double negation." 24 | :autocorrect true 25 | :replace '(if ?x ?y ?z)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/next_next.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.next-next 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/next-next 12 | "`nnext` is succinct and meaningful. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (next (next coll)) 18 | 19 | ; prefer 20 | (nnext coll) 21 | " 22 | {:pattern '(next (next ?coll)) 23 | :message "Use `nnext` instead of recreating it." 24 | :autocorrect true 25 | :replace '(nnext ?coll)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/eq_nil.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.eq-nil 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/eq-nil 12 | "`nil?` exists so use it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (= nil x) 18 | (= x nil) 19 | 20 | ; prefer 21 | (nil? x) 22 | " 23 | {:patterns ['(= nil ?x) 24 | '(= ?x nil)] 25 | :message "Use `nil?` instead of recreating it." 26 | :autocorrect true 27 | :replace '(nil? ?x)}) 28 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/first_next.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.first-next 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/first-next 12 | "`fnext` is succinct and meaningful. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (first (next coll)) 18 | 19 | ; prefer 20 | (fnext coll) 21 | " 22 | {:pattern '(first (next ?coll)) 23 | :message "Use `fnext` instead of recreating it." 24 | :autocorrect true 25 | :replace '(fnext ?coll)}) 26 | -------------------------------------------------------------------------------- /tasks/new_rule_test.tmpl: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.{{genre}}.{{rule-name}}-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name '{{genre}}/{{rule-name}}) 13 | 14 | (defdescribe {{rule-name}}-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form nil 19 | :message "" 20 | :alt nil}] 21 | "()" 22 | (single-rule-config rule-name)))) 23 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/conj_vector.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.conj-vector 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/conj-vector 12 | "`vector` is succinct and meaningful. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (conj [] :a b {:c 1}) 18 | 19 | ; prefer 20 | (vector :a b {:c 1}) 21 | " 22 | {:pattern '(conj [] ?+x) 23 | :message "Use `vector` instead of recreating it." 24 | :autocorrect true 25 | :replace '(vector ?x)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/first_first.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.first-first 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/first-first 12 | "ffirst is succinct and meaningful. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (first (first coll)) 18 | 19 | ; prefer 20 | (ffirst coll) 21 | " 22 | {:pattern '(first (first ?coll)) 23 | :message "Use `ffirst` instead of recreating it." 24 | :autocorrect true 25 | :replace '(ffirst ?coll)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/next_first.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.next-first 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/next-first 12 | "`nfirst` is succinct and meaningful. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (next (first coll)) 18 | 19 | ; prefer 20 | (nfirst coll) 21 | " 22 | {:pattern '(next (first ?coll)) 23 | :message "Use `nfirst` instead of recreating it." 24 | :autocorrect true 25 | :replace '(nfirst ?coll)}) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/divide_by_one_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.divide-by-one-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/divide-by-one) 13 | 14 | (defdescribe divide-by-1-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(/ x 1) 19 | :alt 'x}] 20 | "(/ x 1)" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/eq_true.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.eq-true 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/eq-true 12 | "`true?` exists so use it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (= true x) 18 | (= x true) 19 | 20 | ; prefer 21 | (true? x) 22 | " 23 | {:patterns ['(= true ?x) 24 | '(= ?x true)] 25 | :message "Use `true?` instead of recreating it." 26 | :autocorrect true 27 | :replace '(true? ?x)}) 28 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/neg_checks.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.neg-checks 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/neg-checks 12 | "`neg?` exists so use it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (< num 0) 18 | (> 0 num) 19 | 20 | ; prefer 21 | (neg? num) 22 | " 23 | {:patterns ['(< ?x 0) 24 | '(> 0 ?x)] 25 | :message "Use `neg?` instead of recreating it." 26 | :autocorrect true 27 | :replace '(neg? ?x)}) 28 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/pos_checks.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.pos-checks 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/pos-checks 12 | "`pos?` exists so use it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (< 0 num) 18 | (> num 0) 19 | 20 | ; prefer 21 | (pos? num) 22 | " 23 | {:patterns ['(< 0 ?x) 24 | '(> ?x 0)] 25 | :message "Use `pos?` instead of recreating it." 26 | :autocorrect true 27 | :replace '(pos? ?x)}) 28 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/when_not_not.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.when-not-not 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/when-not-not 12 | "Two `not`s cancel each other out. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (when-not (not x) y z) 18 | 19 | ; prefer 20 | (when x y z) 21 | " 22 | {:pattern '(when-not (not ?x) ?*y) 23 | :message "Use `when` instead of double negation." 24 | :autocorrect true 25 | :replace '(when ?x ?y)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/performance/avoid_satisfies.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.performance.avoid-satisfies 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule performance/avoid-satisfies 12 | "Avoid use of `satisfies?` as it is extremely slow. Restructure your code to rely on protocols or other polymorphic forms. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (satisfies? Foo :bar) 18 | " 19 | {:pattern '(satisfies? ?protocol ?obj) 20 | :message "Avoid using `satisfies?`."}) 21 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/eq_false.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.eq-false 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/eq-false 12 | "`false?` exists so use it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (= false x) 18 | (= x false) 19 | 20 | ; prefer 21 | (false? x) 22 | " 23 | {:patterns ['(= false ?x) 24 | '(= ?x false)] 25 | :message "Use `false?` instead of recreating it." 26 | :autocorrect true 27 | :replace '(false? ?x)}) 28 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/loop_do_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.loop-do-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/loop-do) 13 | 14 | (defdescribe loop-do-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name 'lint/loop-do 18 | :form '(loop [] (do a b)) 19 | :alt '(loop [] a b)}] 20 | "(loop [] (do a b))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/plus_one.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.plus-one 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/plus-one 12 | "Checks for simple +1 that should use `clojure.core/inc`. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (+ x 1) 18 | (+ 1 x) 19 | 20 | ; prefer 21 | (inc x) 22 | " 23 | {:patterns ['(+ ?x 1) 24 | '(+ 1 ?x)] 25 | :message "Use `inc` instead of recreating it." 26 | :autocorrect true 27 | :replace '(inc ?x)}) 28 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/when_not_call.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.when-not-call 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/when-not-call 12 | "`when-not` exists so use it lol. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (when (not x) :a :b :c) 18 | 19 | ; prefer 20 | (when-not x :a :b :c) 21 | " 22 | {:pattern '(when (not ?x) ?*y) 23 | :message "Use `when-not` instead of recreating it." 24 | :autocorrect true 25 | :replace '(when-not ?x ?y)}) 26 | -------------------------------------------------------------------------------- /.splint.edn: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | { 6 | dev {:enabled true} 7 | lint {:enabled true} 8 | metrics {:enabled true} 9 | naming {:enabled true} 10 | performance {:enabled true} 11 | style {:enabled true} 12 | 13 | lint/prefer-method-values {:enabled false} ; gotta wait for babashka 14 | lint/thread-macro-one-arg {:enabled false} 15 | metrics/fn-length {:enabled false} 16 | metrics/parameter-count {:count 5} 17 | style/set-literal-as-fn {:enabled false} 18 | 19 | ; performance/assoc-many {:excludes ["glob:**/path_matcher.clj"]} 20 | ; global {:excludes ["glob:src/noahtheduke/splint/path_matcher.clj"]} 21 | } 22 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/next_next_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.next-next-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/next-next) 13 | 14 | (defdescribe next-next-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(next (next coll)) 19 | :alt '(nnext coll)}] 20 | "(next (next coll))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/dorun_map_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.dorun-map-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/dorun-map) 13 | 14 | (defdescribe dorun-map-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name 'lint/dorun-map 18 | :form '(dorun (map f coll)) 19 | :alt '(run! f coll)}] 20 | "(dorun (map f coll))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/first_next_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.first-next-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/first-next) 13 | 14 | (defdescribe first-next-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(first (next coll)) 19 | :alt '(fnext coll)}] 20 | "(first (next coll))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/next_first_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.next-first-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/next-first) 13 | 14 | (defdescribe next-first-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(next (first coll)) 19 | :alt '(nfirst coll)}] 20 | "(next (first coll))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/not_eq_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.not-eq-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/not-eq) 13 | 14 | (defdescribe not-eq-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(not (= arg1 arg2 arg3)) 19 | :alt '(not= arg1 arg2 arg3)}] 20 | "(not (= arg1 arg2 arg3))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/loop_do.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.loop-do 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/loop-do 12 | "`loop` has an implicit `do`. Use it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (loop [] (do (println 1) (println 2))) 18 | 19 | ; prefer 20 | (loop [] (println 1) (println 2)) 21 | " 22 | {:pattern '(loop ?binding (do ?*exprs)) 23 | :message "Unnecessary `do` in `loop` body." 24 | :autocorrect true 25 | :replace '(loop ?binding ?exprs)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/not_some_pred.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.not-some-pred 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/not-some-pred 12 | "not-any? is succinct and meaningful. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (not (some even? coll)) 18 | 19 | ; prefer 20 | (not-any? even? coll) 21 | " 22 | {:pattern '(not (some ?pred ?coll)) 23 | :message "Use `not-any?` instead of recreating it." 24 | :autocorrect true 25 | :replace '(not-any? ?pred ?coll)}) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/if_not_both_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.if-not-both-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/if-not-both) 13 | 14 | (defdescribe if-not-x-y-x-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name 'lint/if-not-both 18 | :form '(if (not x) y z) 19 | :alt '(if-not x y z)}] 20 | "(if (not x) y z)" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/if_not_not_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.if-not-not-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/if-not-not) 13 | 14 | (defdescribe if-not-not-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name 'lint/if-not-not 18 | :form '(if-not (not x) y z) 19 | :alt '(if x y z)}] 20 | "(if-not (not x) y z)" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/first_first_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.first-first-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/first-first) 13 | 14 | (defdescribe first-first-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(first (first coll)) 19 | :alt '(ffirst coll)}] 20 | "(first (first coll))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/when_not_call_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.when-not-call-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/when-not-call) 13 | 14 | (defdescribe when-not-x-y-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(when (not x) y) 19 | :alt '(when-not x y)}] 20 | "(when (not x) y)" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/when_do.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.when-do 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/when-do 12 | "`when` already defines an implicit `do`. Rely on it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (when x (do (println :a) (println :b) :c)) 18 | 19 | ; prefer 20 | (when x (println :a) (println :b) :c) 21 | " 22 | {:pattern '(when ?x (do ?*y)) 23 | :message "Unnecessary `do` in `when` body." 24 | :autocorrect true 25 | :replace '(when ?x ?y)}) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/if_nil_else_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.if-nil-else-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/if-nil-else) 13 | 14 | (defdescribe if-nil-else-test 15 | (it "matches correctly" 16 | (expect-match 17 | [{:rule-name 'lint/if-nil-else 18 | :form '(if x nil y) 19 | :alt '(when-not x y)}] 20 | "(if x nil y)" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/let_do.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.let-do 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/let-do 12 | "`let` has an implicit `do`, so use it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (let [a 1 b 2] (do (println a) (println b))) 18 | 19 | ; prefer 20 | (let [a 1 b 2] (println a) (println b)) 21 | " 22 | {:pattern '(let ?binding (do ?*exprs)) 23 | :message "Unnecessary `do` in `let` body." 24 | :autocorrect true 25 | :replace '(let ?binding ?exprs)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/mapcat_apply_apply.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.mapcat-apply-apply 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/mapcat-apply-apply 12 | "Check for `(apply concat (apply map x y))`. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (apply concat (apply map x y)) 18 | 19 | ; prefer 20 | (mapcat x y) 21 | " 22 | {:pattern '(apply concat (apply map ?x ?y)) 23 | :message "Use `mapcat` instead of recreating it." 24 | :autocorrect true 25 | :replace '(mapcat ?x ?y)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/filter_vec_filterv.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.filter-vec-filterv 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/filter-vec-filterv 12 | "filterv is preferable for using transients. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (vec (filter pred coll)) 18 | 19 | ; prefer 20 | (filterv pred coll) 21 | " 22 | {:pattern '(vec (filter ?pred ?coll)) 23 | :message "Use `filterv` instead of recreating it." 24 | :autocorrect true 25 | :replace '(filterv ?pred ?coll)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/if_nil_else.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.if-nil-else 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/if-nil-else 12 | "Idiomatic `if` defines both branches. `when-not` returns `nil` in the truthy branch. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (if (some-func) nil :a) 18 | 19 | ; prefer 20 | (when-not (some-func) :a) 21 | " 22 | {:pattern '(if ?x nil ?y) 23 | :message "Use `when-not` instead of recreating it." 24 | :autocorrect true 25 | :replace '(when-not ?x ?y)}) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/conj_vector_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.conj-vector-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/conj-vector) 13 | 14 | (defdescribe conj-vec-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(conj [] x) 19 | :message "Use `vector` instead of recreating it." 20 | :alt '(vector x)}] 21 | "(conj [] x)" 22 | (single-rule-config rule-name)))) 23 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/update_in_assoc_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.update-in-assoc-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/update-in-assoc) 13 | 14 | (defdescribe update-in-assoc-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(update-in coll ks assoc v) 19 | :alt '(assoc-in coll ks v)}] 20 | "(update-in coll ks assoc v)" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/if_not_do.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.if-not-do 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/if-not-do 12 | "`when-not` already defines an implicit `do`. Rely on it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (if-not x (do (println :a) (println :b) :c)) 18 | 19 | ; prefer 20 | (when-not x (println :a) (println :b) :c) 21 | " 22 | {:pattern '(if-not ?x (do ?*y) (?? _ nil?)) 23 | :message "Use `when-not` instead of recreating it." 24 | :autocorrect true 25 | :replace '(when-not ?x ?y)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/apply_str_reverse.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.apply-str-reverse 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/apply-str-reverse 12 | "Check for round-about `clojure.string/reverse`. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (apply str (reverse x)) 18 | 19 | ; prefer 20 | (clojure.string/reverse x) 21 | " 22 | {:pattern '(apply str (reverse ?x)) 23 | :message "Use `clojure.string/reverse` instead of recreating it." 24 | :autocorrect true 25 | :replace '(clojure.string/reverse ?x)}) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/duplicate_field_name_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.duplicate-field-name-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/duplicate-field-name) 13 | 14 | (defdescribe duplicate-field-name-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name 'lint/duplicate-field-name 18 | :alt nil 19 | :message "Duplicate field has been found"}] 20 | "(defrecord Foo [a b a])" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/take_repeatedly_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.take-repeatedly-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/take-repeatedly) 13 | 14 | (defdescribe take-repeatedly-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name 'lint/take-repeatedly 18 | :form '(take n (repeatedly coll)) 19 | :alt '(repeatedly n coll)}] 20 | "(take n (repeatedly coll))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/apply_str_reverse_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.apply-str-reverse-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/apply-str-reverse) 13 | 14 | (defdescribe str-apply-reverse-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(apply str (reverse x)) 19 | :alt '(clojure.string/reverse x)}] 20 | "(apply str (reverse x))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/take_repeatedly.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.take-repeatedly 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/take-repeatedly 12 | "`repeatedly` has an arity for limiting the number of repeats with `take`. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (take 5 (repeatedly (range 10)) 18 | 19 | ; prefer 20 | (repeatedly 5 (range 10)) 21 | " 22 | {:pattern '(take ?n (repeatedly ?coll)) 23 | :message "Rely on the `n` arity of `repeatedly`." 24 | :autocorrect true 25 | :replace '(repeatedly ?n ?coll)}) 26 | -------------------------------------------------------------------------------- /tasks/new_rule.tmpl: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.{{genre}}.{{rule-name}} 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defrule {{genre}}/{{rule-name}} 13 | "Docstring goes here. 14 | 15 | @examples 16 | 17 | ; avoid 18 | (+ 1 1) 19 | 20 | ; prefer 21 | (inc 1) 22 | " 23 | {:pattern '() 24 | :message "" 25 | :replace '() 26 | :on-match (fn [ctx rule form {:syms []}] 27 | (let [new-form '()] 28 | (->diagnostic ctx rule form {:replace-form new-form})))}) 29 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/mapcat_apply_apply_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.mapcat-apply-apply-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/mapcat-apply-apply) 13 | 14 | (defdescribe mapcat-apply-apply-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(apply concat (apply map x y)) 19 | :alt '(mapcat x y)}] 20 | "(apply concat (apply map x y))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/mapcat_concat_map.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.mapcat-concat-map 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/mapcat-concat-map 12 | "Check for `(apply concat (map x y z))`. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (apply concat (map x y)) 18 | (apply concat (map x y z)) 19 | 20 | ; prefer 21 | (mapcat x y) 22 | (mapcat x y z) 23 | " 24 | {:pattern '(apply concat (map ?x ?*y)) 25 | :message "Use `mapcat` instead of recreating it." 26 | :autocorrect true 27 | :replace '(mapcat ?x ?y)}) 28 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/when_not_do.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.when-not-do 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/when-not-do 12 | "`when-not` already defines an implicit `do`. Rely on it. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (when-not x (do (println :a) (println :b) :c)) 18 | 19 | ; prefer 20 | (when-not x (println :a) (println :b) :c) 21 | " 22 | {:pattern '(when-not ?x (do ?*y) (?? _ nil?)) 23 | :message "Unnecessary `do` in `when-not` body." 24 | :autocorrect true 25 | :replace '(when-not ?x ?y)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/if_let_else_nil.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.if-let-else-nil 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/if-let-else-nil 12 | "Idiomatic `if-let` defines both branches. `when-let` returns `nil` in the else branch. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (if-let [a 1] a nil) 18 | 19 | ; prefer 20 | (when-let [a 1] a) 21 | " 22 | {:pattern '(if-let ?binding ?expr (?? _ nil?)) 23 | :message "Use `when-let` instead of recreating it." 24 | :autocorrect true 25 | :replace '(when-let ?binding ?expr)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/apply_str_interpose.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.apply-str-interpose 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/apply-str-interpose 12 | "Check for round-about `clojure.string/join`. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (apply str (interpose \",\" x)) 18 | 19 | ; prefer 20 | (clojure.string/join \",\" x) 21 | " 22 | {:pattern '(apply str (interpose ?x ?y)) 23 | :message "Use `clojure.string/join` instead of recreating it." 24 | :autocorrect true 25 | :replace '(clojure.string/join ?x ?y)}) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/apply_str_interpose_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.apply-str-interpose-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/apply-str-interpose) 13 | 14 | (defdescribe str-apply-interpose-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(apply str (interpose x y)) 19 | :alt '(clojure.string/join x y)}] 20 | "(apply str (interpose x y))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/nested_addition_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.nested-addition-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/nested-addition) 13 | 14 | (defdescribe nested-addition-test 15 | (it "works" 16 | (expect-match 17 | [{:alt '(+ x y z)}] 18 | "(+ x (+ y z))" 19 | (single-rule-config rule-name)) 20 | (expect-match 21 | [{:alt '(+ x y z a)}] 22 | "(+ x (+ y z a))" 23 | (single-rule-config rule-name)))) 24 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/nested_multiply_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.nested-multiply-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/nested-multiply) 13 | 14 | (defdescribe nested-multiply-test 15 | (it "works" 16 | (expect-match 17 | [{:alt '(* x y z)}] 18 | "(* x (* y z))" 19 | (single-rule-config rule-name)) 20 | (expect-match 21 | [{:alt '(* x y z a)}] 22 | "(* x (* y z a))" 23 | (single-rule-config rule-name)))) 24 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/trivial_for.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.trivial-for 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/trivial-for 12 | "`for` is a complex and weighty macro. When simply applying a function to each element, better to rely on other built-ins. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (for [item items] 18 | (f item)) 19 | 20 | ; prefer 21 | (map f items) 22 | " 23 | {:pattern '(for [?item ?items] (?f ?item)) 24 | :message "Avoid trivial usage of `for`." 25 | :autocorrect true 26 | :replace '(map ?f ?items)}) 27 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/redundant_let_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.redundant-let-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/redundant-let) 13 | 14 | (defdescribe redundant-let-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name 'style/redundant-let 18 | :form '(let [a 1] (let [b 2] (println a b))) 19 | :alt '(let [a 1 b 2] (println a b))}] 20 | "(let [a 1] (let [b 2] (println a b)))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/eq_zero.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.eq-zero 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defn eq? [form] 12 | ('#{= ==} form)) 13 | 14 | (defrule style/eq-zero 15 | "`zero?` exists so use it. 16 | 17 | @examples 18 | 19 | ; avoid 20 | (= 0 num) 21 | (= num 0) 22 | (== 0 num) 23 | (== num 0) 24 | 25 | ; prefer 26 | (zero? num) 27 | " 28 | {:patterns ['((? _ eq?) 0 ?x) 29 | '((? _ eq?) ?x 0)] 30 | :message "Use `zero?` instead of recreating it." 31 | :autocorrect true 32 | :replace '(zero? ?x)}) 33 | 34 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/performance/avoid_satisfies_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.performance.avoid-satisfies-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'performance/avoid-satisfies) 13 | 14 | (defdescribe avoid-satisfies-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(satisfies? Foo :bar) 19 | :message "Avoid using `satisfies?`." 20 | :alt nil}] 21 | "(satisfies? Foo :bar)" 22 | (single-rule-config rule-name)))) 23 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/nested_addition.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.nested-addition 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defn +? [form] 12 | (case form 13 | (+ +') true 14 | false)) 15 | 16 | (defrule style/nested-addition 17 | "Checks for simple nested additions. 18 | 19 | @examples 20 | 21 | ; avoid 22 | (+ x (+ y z)) 23 | (+ x (+ y z a)) 24 | 25 | ; prefer 26 | (+ x y z) 27 | (+ x y z a) 28 | " 29 | {:pattern '((? p +?) ?x (?p ?*xs)) 30 | :message "Use the variadic arity of `+`." 31 | :autocorrect true 32 | :replace '(?p ?x ?xs)}) 33 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/nested_multiply.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.nested-multiply 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defn *? [form] 12 | (case form 13 | (* *') true 14 | false)) 15 | 16 | (defrule style/nested-multiply 17 | "Checks for simple nested multiply. 18 | 19 | @examples 20 | 21 | ; avoid 22 | (* x (* y z)) 23 | (* x (* y z a)) 24 | 25 | ; prefer 26 | (* x y z) 27 | (* x y z a) 28 | " 29 | {:pattern '((? p *?) ?x (?p ?*xs)) 30 | :message "Use the variadic arity of `*`." 31 | :autocorrect true 32 | :replace '(?p ?x ?xs)}) 33 | -------------------------------------------------------------------------------- /scripts/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z "$GRAALVM_HOME" ]; then 4 | echo 'Please set GRAALVM_HOME' 5 | exit 1 6 | fi 7 | 8 | VERSION=$(cat resources/noahtheduke/splint/SPLINT_VERSION) 9 | 10 | # Ensure Graal native-image program is installed 11 | "$GRAALVM_HOME/bin/gu" install native-image 12 | 13 | rm -rf classes 14 | mkdir classes 15 | clojure -M -e "(compile 'noahtheduke.splint)" 16 | 17 | "$GRAALVM_HOME/bin/native-image" \ 18 | -cp "$(clojure -Spath):classes" \ 19 | -H:Name="splint-$VERSION" \ 20 | -H:+ReportExceptionStackTraces \ 21 | -H:IncludeResources="noahtheduke/splint/SPLINT_VERSION" \ 22 | -H:IncludeResources="noahtheduke/splint/config/default.edn" \ 23 | --verbose \ 24 | --no-fallback \ 25 | --no-server \ 26 | "-J-Xmx3g" \ 27 | noahtheduke.splint 28 | 29 | mv "splint-$VERSION" target/ 30 | rm "splint-$VERSION.build_artifacts.txt" 31 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/when_not_do_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.when-not-do-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/when-not-do) 13 | 14 | (defdescribe when-not-do-test 15 | (it "works with multiple args" 16 | (expect-match 17 | [{:alt '(when-not x y z)}] 18 | "(when-not x (do y z))" 19 | (single-rule-config rule-name)) 20 | (expect-match 21 | [{:alt '(when-not x y z)}] 22 | "(when-not x (do y z) nil)" 23 | (single-rule-config rule-name)))) 24 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/try_splicing_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.try-splicing-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/try-splicing) 13 | 14 | (defdescribe try-splicing-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name 'lint/try-splicing 18 | :form '(try (splint/unquote-splicing body) (finally :true)) 19 | :alt '(try (do (splint/unquote-splicing body)) (finally :true))}] 20 | "(try ~@body (finally :true))" 21 | (single-rule-config rule-name)))) 22 | -------------------------------------------------------------------------------- /dev/user.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns user 6 | (:require 7 | [clj-java-decompiler.core :as decompiler] 8 | [clojure.java.javadoc] 9 | [clojure.pprint] 10 | [clojure.repl] 11 | [clojure.tools.namespace.repl :as tns] 12 | [criterium.core :as criterium] 13 | [noahtheduke.splint.dev] 14 | [potemkin :refer [import-vars]])) 15 | 16 | (set! *warn-on-reflection* true) 17 | 18 | (import-vars 19 | [clojure.repl 20 | source apropos dir pst doc find-doc] 21 | [clojure.java.javadoc 22 | javadoc] 23 | [clojure.pprint 24 | pp pprint] 25 | [clojure.tools.namespace.repl 26 | refresh-all] 27 | [clj-java-decompiler.core 28 | decompile] 29 | [criterium.core 30 | quick-bench 31 | bench]) 32 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/prefer_boolean.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.prefer-boolean 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/prefer-boolean 12 | "Use `boolean` if you must return `true` or `false` from an expression. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (if some-val true false) 18 | (if (some-func) true false) 19 | 20 | ; prefer 21 | (boolean some-val) 22 | (boolean (some-func))" 23 | {:pattern '(if ?test-expr true false) 24 | :message "Use `boolean` if you must return `true` or `false`." 25 | :autocorrect true 26 | :replace '(boolean ?test-expr)}) 27 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/update_in_assoc.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.update-in-assoc 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/update-in-assoc 12 | "`update-in`-ing an `assoc` with the same key are hard to read. `assoc-in` is known 13 | and idiomatic. 14 | 15 | @examples 16 | 17 | ; avoid 18 | (update-in coll [:a :b] assoc 5) 19 | 20 | ; prefer 21 | (assoc-in coll [:a :b] 5) 22 | " 23 | {:pattern '(update-in ?coll ?keys assoc ?val) 24 | :message "Use `assoc-in` instead of recreating it." 25 | :autocorrect true 26 | :replace '(assoc-in ?coll ?keys ?val)}) 27 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/not_nil_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.not-nil-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/not-nil?) 13 | 14 | (defdescribe not-nil?-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(not (nil? x)) 19 | :alt '(some? x)}] 20 | "(not (nil? x))" 21 | (single-rule-config rule-name))) 22 | (it "ignores plain nil" 23 | (expect-match 24 | nil 25 | "(not nil)" 26 | (single-rule-config rule-name)))) 27 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/prefer_vary_meta.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.prefer-vary-meta 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/prefer-vary-meta 12 | "`vary-meta` works like `swap!`, so no need to access and overwrite in two steps. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (with-meta x (assoc (meta x) :filename filename)) 18 | 19 | ; prefer 20 | (vary-meta x assoc :filename filename) 21 | " 22 | {:pattern '(with-meta ?x (?f (meta ?x) ?*args)) 23 | :message "Use `vary-meta` instead of recreating it." 24 | :autocorrect true 25 | :replace '(vary-meta ?x ?f ?args)}) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/when_not_empty.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.when-not-empty 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/when-not-empty? 12 | "`seq` returns `nil` when given an empty collection. `empty?` is implemented as 13 | `(not (seq coll))` so it's best and fastest to use `seq` directly. 14 | 15 | @examples 16 | 17 | ; avoid 18 | (when-not (empty? ?x) &&. ?y) 19 | 20 | ; prefer 21 | (when (seq ?x) &&. ?y) 22 | " 23 | {:pattern '(when-not (empty? ?x) ?*y) 24 | :message "`seq` is idiomatic, gotta learn to love it." 25 | :autocorrect true 26 | :replace '(when (seq ?x) ?y)}) 27 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/filter_vec_filterv_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.filter-vec-filterv-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/filter-vec-filterv) 13 | 14 | (defdescribe filter-vec-filterv-test 15 | (it "looks for filter" 16 | (expect-match 17 | [{:alt '(filterv pred coll)}] 18 | "(vec (filter pred coll))" 19 | (single-rule-config rule-name))) 20 | (it "ignores filterv" 21 | (expect-match 22 | nil 23 | "(vec (filterv pred coll))" 24 | (single-rule-config rule-name)))) 25 | -------------------------------------------------------------------------------- /docs/dev/todo.md: -------------------------------------------------------------------------------- 1 | # Todo 2 | 3 | * [x] Move spat.parser to splint.parser. 4 | * [x] Find a new home for simple-type. 5 | * [x] Read deps.edn file and check the src paths by default. 6 | * [] Research making `runner/check-files!` a pure function. 7 | 8 | ## Rule ideas 9 | 10 | * [] Require that `cond` includes `:else nil` if left out. 11 | * [] `(if (seq x) (...) [])` -> `(when (seq x) ...)` 12 | * [] `(if (seq x) x (...))` -> `(or (not-empty x) ...)` 13 | * [] Warn on mixing `->` and `->>`. Suggest `as->`? 14 | * [] Duplicate case test entry: `(case x :a 1 :a 2 :b 3)` 15 | * [] Quoted case entry: `(case x 'a 1)` 16 | * [] Check protocol method varargs: `(defprotocol Foo (bar [x & xs]))` 17 | 18 | performance genre 19 | 20 | * [x] `(assoc m :k1 v1 :k2 v2 ...)` -> `(-> m (assoc :k1 v1) (assoc :k2 v2) ...)` 21 | * [x] `(get-in m [:k1 :k2 :k3])` -> `(-> m :k1 :k2 :k3)` 22 | * [x] `(get m :k)` -> `(:k m)` 23 | * [x] `(satisfies? Foo :bar)` -> disallow 24 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/missing_body_in_when.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.missing-body-in-when 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defrule lint/missing-body-in-when 13 | "`when` calls should have at least 1 expression after the condition. 14 | 15 | @examples 16 | 17 | ; avoid 18 | (when true) 19 | (when (some-func)) 20 | 21 | ; prefer 22 | (when true (do-stuff)) 23 | (when (some-func) (do-stuff)) 24 | " 25 | {:pattern '(when _) 26 | :message "Missing body in when" 27 | :on-match (fn [ctx rule form _] (->diagnostic ctx rule form))}) 28 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/minus_zero_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.minus-zero-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/minus-zero) 13 | 14 | (defdescribe minus-0-test 15 | (it "expects 0 to be in final position" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(- x 0) 19 | :alt 'x}] 20 | "(- x 0)" 21 | (single-rule-config rule-name))) 22 | (it "ignores multi-arity minus" 23 | (expect-match 24 | nil 25 | "(- x y 0)" 26 | (single-rule-config rule-name)))) 27 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/minus_one_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.minus-one-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/minus-one) 13 | 14 | (defdescribe minus-x-1-test 15 | (it "expects 1 to be in final position" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(- x 1) 19 | :alt '(dec x)}] 20 | "(- x 1)" 21 | (single-rule-config rule-name))) 22 | (it "ignores multi-arity minus" 23 | (expect-match 24 | nil 25 | "(- x y x 1)" 26 | (single-rule-config rule-name)))) 27 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/apply_str.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.apply-str 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defn not-special? [form] 12 | (if (list? form) 13 | (not (#{'reverse 'interpose} (first form))) 14 | true)) 15 | 16 | (defrule style/apply-str 17 | "Check for round-about `clojure.string/join`. 18 | 19 | @examples 20 | 21 | ; avoid 22 | (apply str x) 23 | 24 | ; prefer 25 | (clojure.string/join x) 26 | " 27 | {:pattern '(apply str (? coll not-special?)) 28 | :message "Use `clojure.string/join` instead of recreating it." 29 | :autocorrect true 30 | :replace '(clojure.string/join ?coll)}) 31 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/prefer_vary_meta_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.prefer-vary-meta-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/prefer-vary-meta) 13 | 14 | (defdescribe prefer-vary-meta-test 15 | (it "works" 16 | (expect-match 17 | [{:alt '(vary-meta x f args)}] 18 | "(with-meta x (f (meta x) args))" 19 | (single-rule-config rule-name))) 20 | (it "ignores direct non-meta calls" 21 | (expect-match 22 | nil 23 | "(let [xm (meta x)] (with-meta x (f xm args)))" 24 | (single-rule-config rule-name)))) 25 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/if_same_truthy_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.if-same-truthy-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/if-same-truthy) 13 | 14 | (defdescribe if-x-x-y-test 15 | (it "respects 3 arity" 16 | (expect-match 17 | [{:rule-name 'lint/if-same-truthy 18 | :form '(if x x y) 19 | :alt '(or x y)}] 20 | "(if x x y)" 21 | (single-rule-config rule-name))) 22 | (it "ignores when no match" 23 | (expect-match nil 24 | "(if false (reset! state true) (go a))" 25 | (single-rule-config rule-name)))) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/multiply_by_one_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.multiply-by-one-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/multiply-by-one) 13 | 14 | (defdescribe multiply-by-1-test 15 | (it "works in either order" 16 | (expect-match '[{:alt x}] "(* x 1)" (single-rule-config rule-name)) 17 | (expect-match '[{:alt x}] "(* 1 x)" (single-rule-config rule-name))) 18 | (it "ignores multi-arity multiply" 19 | (expect-match nil "(* x y 1)" (single-rule-config rule-name)) 20 | (expect-match nil "(* 1 x y)" (single-rule-config rule-name)))) 21 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/multiply_by_zero_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.multiply-by-zero-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/multiply-by-zero) 13 | 14 | (defdescribe multiply-by-1-test 15 | (it "works in either order" 16 | (expect-match [{:alt 0}] "(* x 0)" (single-rule-config rule-name)) 17 | (expect-match [{:alt 0}] "(* 0 x)" (single-rule-config rule-name))) 18 | (it "ignores multi-arity multiply" 19 | (expect-match nil "(* x y 0)" (single-rule-config rule-name)) 20 | (expect-match nil "(* 0 x y)" (single-rule-config rule-name)))) 21 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/plus_zero_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.plus-zero-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/plus-zero) 13 | 14 | (defdescribe plus-x-0-test 15 | (it "understands 0 in either position" 16 | (expect-match 17 | [{:alt 'x}] 18 | "(+ x 0)" 19 | (single-rule-config rule-name)) 20 | (expect-match 21 | [{:alt 'x}] 22 | "(+ 0 x)" 23 | (single-rule-config rule-name))) 24 | (it "ignores multi-arity plus" 25 | (expect-match 26 | nil 27 | "(+ x y 0)" 28 | (single-rule-config rule-name)))) 29 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/eq_nil_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.eq-nil-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/eq-nil) 13 | 14 | (defdescribe eq-nil-test 15 | (it "checks nil first" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(= nil x) 19 | :alt '(nil? x)}] 20 | "(= nil x)" 21 | (single-rule-config rule-name))) 22 | (it "checks nil second" 23 | (expect-match 24 | [{:rule-name rule-name 25 | :form '(= x nil) 26 | :alt '(nil? x)}] 27 | "(= x nil)" 28 | (single-rule-config rule-name)))) 29 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/let_do_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.let-do-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/let-do) 13 | 14 | (defdescribe let-do-test 15 | (it "works with one child" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(let [a 1 b 2] (do a b)) 19 | :alt '(let [a 1 b 2] a b)}] 20 | "(let [a 1 b 2] (do a b))" 21 | (single-rule-config rule-name))) 22 | (it "ignores multiple children" 23 | (expect-match 24 | nil 25 | "(let [a 1 b 2] (do a b) (do a b))" 26 | (single-rule-config rule-name)))) 27 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/plus_one_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.plus-one-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/plus-one) 13 | 14 | (defdescribe plus-x-1-test 15 | (it "understands 1 in either position" 16 | (expect-match 17 | [{:alt '(inc x)}] 18 | "(+ x 1)" 19 | (single-rule-config rule-name)) 20 | (expect-match 21 | [{:alt '(inc x)}] 22 | "(+ 1 x)" 23 | (single-rule-config rule-name))) 24 | (it "ignores multi-arity plus" 25 | (expect-match 26 | nil 27 | "(+ x y 1)" 28 | (single-rule-config rule-name)))) 29 | -------------------------------------------------------------------------------- /docs/cljdoc.edn: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | {:cljdoc/languages ["clj"] 6 | :cljdoc.doc/tree 7 | [["Home" {:file "README.md"}] 8 | ["Installation" {:file "docs/installation.md"}] 9 | ["Usage" {:file "docs/usage.md"}] 10 | ["Configuration" {:file "docs/configuration.md"}] 11 | ["Rules Overview" {:file "docs/rules-overview.md"} 12 | ["Lint" {:file "docs/rules/lint.md"}] 13 | ["Metrics" {:file "docs/rules/metrics.md"}] 14 | ["Naming" {:file "docs/rules/naming.md"}] 15 | ["Performance" {:file "docs/rules/performance.md"}] 16 | ["Style" {:file "docs/rules/style.md"}]] 17 | ["Custom Rules" 18 | ["Developing a new rule" {:file "docs/develop-new-rule.md"}] 19 | ["Patterns" {:file "docs/patterns.md"}]] 20 | ["Contributing" {:file "CONTRIBUTING.md"}] 21 | ["Changelog" {:file "CHANGELOG.md"}]]} 22 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/loop_empty_when.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.loop-empty-when 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/loop-empty-when 12 | "Empty loops with nested `when` can be `while`. Doesn't apply if the final expr of the `when` isn't `(recur)`, which includes any nested cases (`let`, etc). 13 | 14 | @examples 15 | 16 | ; avoid 17 | (loop [] (when (some-func) (println 1) (println 2) (recur))) 18 | 19 | ; prefer 20 | (while (some-func) (println 1) (println 2)) 21 | " 22 | {:pattern '(loop [] (when ?test ?*exprs (recur))) 23 | :message "Use `while` instead of recreating it." 24 | :autocorrect true 25 | :replace '(while ?test ?exprs)}) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/eq_true_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.eq-true-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/eq-true) 13 | 14 | (defdescribe eq-true-test 15 | (it "checks true first" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(= true x) 19 | :alt '(true? x)}] 20 | "(= true x)" 21 | (single-rule-config rule-name))) 22 | (it "checks true second" 23 | (expect-match 24 | [{:rule-name rule-name 25 | :form '(= x true) 26 | :alt '(true? x)}] 27 | "(= x true)" 28 | (single-rule-config rule-name)))) 29 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/performance/get_in_literals.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.performance.get-in-literals 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule performance/get-in-literals 12 | "`clojure.core/get-in` is both polymorphic and relies on seq stepping, which has heavy overhead when the listed slots are keyword literals. Faster to call them as functions. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (get-in m [:some-key1 :some-key2 :some-key3]) 18 | 19 | ; prefer 20 | (-> m :some-key1 :some-key2 :some-key3) 21 | " 22 | {:pattern '(get-in m [(?+ keys keyword?)]) 23 | :message "Use keywords as functions instead of `get-in`." 24 | :autocorrect true 25 | :replace '(-> m ?keys)}) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/pos_checks_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.pos-checks-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/pos-checks) 13 | 14 | (defdescribe lt-0-x-test 15 | (it "works with less than" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(< 0 x) 19 | :alt '(pos? x)}] 20 | "(< 0 x)" 21 | (single-rule-config rule-name))) 22 | 23 | (it "works with greater than" 24 | (expect-match 25 | [{:rule-name rule-name 26 | :form '(> x 0) 27 | :alt '(pos? x)}] 28 | "(> x 0)" 29 | (single-rule-config rule-name)))) 30 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/let_if.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.let-if 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/let-if 12 | "`if-let` exists so use it. 13 | 14 | @safety 15 | Suggestions can be wrong as there's no code-walking to determine if `result` binding is used in falsy branch. 16 | 17 | @examples 18 | 19 | ; avoid 20 | (let [result (some-func)] (if result (do-stuff result) (other-stuff))) 21 | 22 | ; prefer 23 | (if-let [result (some-func)] (do-stuff result) (other-stuff)) 24 | " 25 | {:pattern '(let [?result ?given] (if ?result ?truthy ?falsy)) 26 | :message "Use `if-let` instead of recreating it." 27 | :replace '(if-let [?result ?given] ?truthy ?falsy)}) 28 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/performance/get_keyword.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.performance.get-keyword 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule performance/get-keyword 12 | "`clojure.core/get` is polymorphic and overkill if accessing a map with a keyword literal. The fastest is to fall the map itself as a function but that requires a `nil` check, so the safest fast method is to use the keyword as function. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (get m :some-key) 18 | 19 | ; prefer 20 | (:some-key m) 21 | " 22 | {:pattern '(get ?m (? k keyword?)) 23 | :message "Use keywords as functions instead of the polymorphic function `get`." 24 | :autocorrect true 25 | :replace '(?k ?m)}) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/eq_false_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.eq-false-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/eq-false) 13 | 14 | (defdescribe eq-false-test 15 | (it "checks false first" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(= false x) 19 | :alt '(false? x)}] 20 | "(= false x)" 21 | (single-rule-config rule-name))) 22 | (it "checks false second" 23 | (expect-match 24 | [{:rule-name rule-name 25 | :form '(= x false) 26 | :alt '(false? x)}] 27 | "(= x false)" 28 | (single-rule-config rule-name)))) 29 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/neg_checks_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.neg-checks-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/neg-checks) 13 | 14 | (defdescribe lt-x-0-test 15 | (it "recognizes < (less than)" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(< x 0) 19 | :alt '(neg? x)}] 20 | "(< x 0)" 21 | (single-rule-config rule-name))) 22 | 23 | (it "recognizes > (greater than)" 24 | (expect-match 25 | [{:rule-name rule-name 26 | :form '(> 0 x) 27 | :alt '(neg? x)}] 28 | "(> 0 x)" 29 | (single-rule-config rule-name)))) 30 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/useless_do_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.useless-do-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/useless-do) 13 | 14 | (defdescribe useless-do-x-test 15 | (it "works" 16 | (expect-match 17 | [{:form '(do x) 18 | :message "Unnecessary `do`." 19 | :alt 'x}] 20 | "(do x)" 21 | (single-rule-config rule-name))) 22 | (it "ignores function literal context" 23 | (expect-match nil "#(do [%1 %2])" (single-rule-config rule-name))) 24 | (it "ignores unquote-splicing context" 25 | (expect-match nil "(do ~@body)" (single-rule-config rule-name)))) 26 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/filter_complement.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.filter-complement 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]] 8 | [noahtheduke.splint.rules.helpers :refer [fn??]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defrule style/filter-complement 13 | "Check for `(filter (complement pred) coll)`. 14 | 15 | @examples 16 | 17 | ; avoid 18 | (filter (complement even?) coll) 19 | 20 | ; prefer 21 | (remove even? coll) 22 | " 23 | {:patterns ['(filter (complement ?pred) ?coll) 24 | '(filter ((? _ fn??) [?arg] (not (?pred ?arg))) ?coll) 25 | '(filter (comp not ?pred) ?coll)] 26 | :message "Use `remove` instead of recreating it." 27 | :autocorrect true 28 | :replace '(remove ?pred ?coll)}) 29 | -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | {:min-bb-version "1.12.205" 6 | :paths ["src" "resources"] 7 | :deps {org.clojure/tools.cli {:mvn/version "1.1.230"} 8 | borkdude/edamame {:mvn/version "1.5.35"} 9 | camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.3"} 10 | fipp/fipp {:mvn/version "0.6.26"} 11 | org.flatland/ordered {:mvn/version "1.15.12"}} 12 | :tasks {splint noahtheduke.splint/-main 13 | lazytest {:extra-deps {io.github.noahtheduke/lazytest {:mvn/version "1.8.0"} 14 | nubank/matcher-combinators {:mvn/version "3.9.1"} 15 | org.clojure/tools.gitlibs {:mvn/version "2.5.197"}} 16 | :extra-paths ["dev" "test"] 17 | :task lazytest.main/-main}} 18 | :bbin/bin {splint {:main-opts ["-m" "noahtheduke.splint"]}}} 19 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/let_when.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.let-when 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/let-when 12 | "`when-let` exists so use it. 13 | 14 | @safety 15 | Suggestions can be wrong as there's no code-walking to determine if `result` binding is used in falsy branch. 16 | 17 | @examples 18 | 19 | ; avoid 20 | (let [result (some-func)] (when result (do-stuff result))) 21 | 22 | ; prefer 23 | (when-let [result (some-func)] (do-stuff result)) 24 | " 25 | {:patterns ['(let [?result ?given] (when ?result ?*args)) 26 | '(let [?result ?given] (if ?result ?args))] 27 | :message "Use `when-let` instead of recreating it." 28 | :replace '(when-let [?result ?given] ?args)}) 29 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/incorrectly_swapped_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.incorrectly-swapped-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/incorrectly-swapped) 13 | 14 | (defdescribe incorrectly-swapped-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '([a b] [a b]) 19 | :message "Looks like an incorrect variable swap." 20 | :alt '([a b] [b a])}] 21 | "(let [a 1 b 2 [a b] [a b]] ...)" 22 | (single-rule-config rule-name))) 23 | (it "works" 24 | (expect-match 25 | nil 26 | "(let [a 1 b 2 [a b] [b a]] ...)" 27 | (single-rule-config rule-name)))) 28 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/trivial_for_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.trivial-for-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/trivial-for) 13 | 14 | (defdescribe trivial-for-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(for [item items] (f item)) 19 | :message "Avoid trivial usage of `for`." 20 | :alt '(map f items)}] 21 | "(for [item items] (f item))" 22 | (single-rule-config rule-name))) 23 | (it "ignores multi-arity f calls" 24 | (expect-match 25 | nil 26 | "(for [item items] (f item other-item))" 27 | (single-rule-config rule-name)))) 28 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/try_splicing.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.try-splicing 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/try-splicing 12 | "A macro that wraps a splicing unquote in a try-catch or try-finally can lead 13 | to subtle hard to debug errors. Better to wrap the splicing unquote in a `do` 14 | to force it into 'expression position'. 15 | 16 | @examples 17 | 18 | ; avoid 19 | `(try ~@body (finally :true)) 20 | 21 | ; prefer 22 | `(try (do ~@body) (finally :true)) 23 | " 24 | {:pattern '(try (splint/unquote-splicing ?body) ?*args) 25 | :message "Wrap splicing unquotes in a `try` in a `do` to catch subtle bugs." 26 | :autocorrect true 27 | :replace '(try (do (splint/unquote-splicing ?body)) ?args)}) 28 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/if_not_do_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.if-not-do-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/if-not-do) 13 | 14 | (defdescribe if-not-do-test 15 | (it "handles 2 arity" 16 | (expect-match 17 | [{:rule-name 'lint/if-not-do 18 | :form '(if-not x (do y z)) 19 | :alt '(when-not x y z)}] 20 | "(if-not x (do y z))" 21 | (single-rule-config rule-name))) 22 | (it "handles 3 arity" 23 | (expect-match 24 | [{:rule-name 'lint/if-not-do 25 | :form '(if-not x (do y z) nil) 26 | :alt '(when-not x y z)}] 27 | "(if-not x (do y z) nil)" 28 | (single-rule-config rule-name)))) 29 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/tostring_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.tostring-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/tostring) 13 | 14 | (defdescribe str-to-string-test 15 | (it "checks dot syntax" 16 | (expect-match 17 | [{:form '(.toString x) 18 | :message "Use `str` instead of interop." 19 | :alt '(str x)}] 20 | "(.toString x)" 21 | (single-rule-config rule-name))) 22 | (it "checks method values syntax" 23 | (expect-match 24 | [{:form '(String/toString x) 25 | :message "Use `str` instead of interop." 26 | :alt '(str x)}] 27 | "(String/toString x)" 28 | (single-rule-config rule-name)))) 29 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/mapcat_concat_map_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.mapcat-concat-map-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/mapcat-concat-map) 13 | 14 | (defdescribe mapcat-concat-map-test 15 | (it "works with multiple map arities" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(apply concat (map x y)) 19 | :alt '(mapcat x y)}] 20 | "(apply concat (map x y))" 21 | (single-rule-config rule-name)) 22 | (expect-match 23 | [{:rule-name rule-name 24 | :form '(apply concat (map x y z)) 25 | :alt '(mapcat x y z)}] 26 | "(apply concat (map x y z))" 27 | (single-rule-config rule-name)))) 28 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/loop_empty_when_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.loop-empty-when-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/loop-empty-when) 13 | 14 | (defdescribe loop-empty-when-test 15 | (it "handles top-level recur" 16 | (expect-match 17 | [{:rule-name 'lint/loop-empty-when 18 | :form '(loop [] (when (= 1 1) (prn 1) (prn 2) (recur))) 19 | :alt '(while (= 1 1) (prn 1) (prn 2))}] 20 | "(loop [] (when (= 1 1) (prn 1) (prn 2) (recur)))" 21 | (single-rule-config rule-name))) 22 | (it "ignores nested recurs" 23 | (expect-match 24 | nil 25 | "(loop [] (when (= 1 1) (prn 1) (prn 2) (do (recur))))" 26 | (single-rule-config rule-name)))) 27 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/apply_str_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.apply-str-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/apply-str) 13 | 14 | (defdescribe apply-str-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(apply str x) 19 | :alt '(clojure.string/join x)}] 20 | "(apply str x)" 21 | (single-rule-config rule-name))) 22 | (it "ignores nested reverse" 23 | (expect-match 24 | nil 25 | "(apply str (reverse x))" 26 | (single-rule-config rule-name))) 27 | (it "ignores nested interpose" 28 | (expect-match 29 | nil 30 | "(apply str (interpose x))" 31 | (single-rule-config rule-name)))) 32 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/not_empty_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.not-empty-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it describe]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/not-empty?) 13 | 14 | (defdescribe not-empty?-test 15 | (describe "chosen style:" 16 | (it ":seq" 17 | (expect-match 18 | [{:rule-name rule-name 19 | :form '(not (empty? x)) 20 | :alt '(seq x)}] 21 | "(not (empty? x))" 22 | (single-rule-config rule-name))) 23 | 24 | (it ":not-empty" 25 | (expect-match 26 | [{:rule-name rule-name 27 | :form '(not (empty? x)) 28 | :alt '(not-empty x)}] 29 | "(not (empty? x))" 30 | (single-rule-config rule-name {:chosen-style :not-empty}))))) 31 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/if_let_else_nil_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.if-let-else-nil-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/if-let-else-nil) 13 | 14 | (defdescribe if-let-else-nil-test 15 | (it "handles no else" 16 | (expect-match 17 | [{:rule-name 'lint/if-let-else-nil 18 | :form '(if-let binding expr) 19 | :alt '(when-let binding expr)}] 20 | "(if-let binding expr)" 21 | (single-rule-config rule-name))) 22 | (it "handles else nil" 23 | (expect-match 24 | [{:rule-name 'lint/if-let-else-nil 25 | :form '(if-let binding expr nil) 26 | :alt '(when-let binding expr)}] 27 | "(if-let binding expr nil)" 28 | (single-rule-config rule-name)))) 29 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/let_if_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.let-if-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/let-if) 13 | 14 | (defdescribe let-if-test 15 | (it "works with else branch" 16 | (expect-match 17 | [{:rule-name 'lint/let-if 18 | :form '(let [result (some-func)] (if result (do-stuff result) (other-stuff))) 19 | :alt '(if-let [result (some-func)] (do-stuff result) (other-stuff))}] 20 | "(let [result (some-func)] (if result (do-stuff result) (other-stuff)))" 21 | (single-rule-config rule-name))) 22 | (it "ignores no else branch" 23 | (expect-match nil 24 | "(let [result (some-func)] (if result (do-stuff result)))" 25 | (single-rule-config rule-name)))) 26 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/locking_object_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.locking-object-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/locking-object) 13 | 14 | (defn config [& {:as style}] 15 | (cond-> (single-rule-config rule-name) 16 | style (update rule-name merge style))) 17 | 18 | (defdescribe locking-object-test 19 | (it "disallows keywords" 20 | (expect-match 21 | [{:rule-name rule-name 22 | :form '(locking :hello (+ 1 1)) 23 | :message "Lock on a symbol bound to (Object.), not a keyword" 24 | :alt nil}] 25 | "(locking :hello (+ 1 1))" 26 | (config))) 27 | (it "allows symbols" 28 | (expect-match 29 | nil 30 | "(locking hello (+ 1 1))" 31 | (config)))) 32 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/duplicate_field_name.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.duplicate-field-name 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defrule lint/duplicate-field-name 13 | "`deftype` and `defrecord` will throw errors if you define multiple fields 14 | with the same name, but it's good to catch these things early too. 15 | 16 | @examples 17 | 18 | ; avoid 19 | (defrecord Foo [a b a]) 20 | 21 | ; prefer 22 | (defrecord Foo [a b c]) 23 | " 24 | {:pattern '(defrecord ?name (? fields vector?) ?*body) 25 | :autocorrect true 26 | :on-match (fn [ctx rule form {:syms [?fields]}] 27 | (when (not= (count ?fields) (count (set ?fields))) 28 | (->diagnostic ctx rule form {:message "Duplicate field has been found"})))}) 29 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/dorun_map.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.dorun-map 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule lint/dorun-map 12 | "`map` is lazy, which carries a performance and memory cost. `dorun` uses `seq` iteration to realize the entire sequence, returning `nil`. This style of iteration also carries a performance and memory cost. `dorun` is intended for more complex sequences, whereas a simple `map` can be accomplished with `reduce` + `conj`. 13 | 14 | `run!` uses `reduce` which non-lazy and has no `seq` overhead. 15 | 16 | @examples 17 | 18 | ; avoid 19 | (dorun (map println (range 10))) 20 | 21 | ; prefer 22 | (run! println (range 10)) 23 | " 24 | {:pattern '(dorun (map ?fn ?coll)) 25 | :message "Use `run!`, a non-lazy function." 26 | :autocorrect true 27 | :replace '(run! ?fn ?coll)}) 28 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/prefer_boolean_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.prefer-boolean-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/prefer-boolean) 13 | 14 | (defdescribe prefer-boolean-test 15 | (it "works with symbol predicates" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(if some-val true false) 19 | :alt '(boolean some-val)}] 20 | "(if some-val true false)" 21 | (single-rule-config rule-name))) 22 | (it "works with function predicates" 23 | (expect-match 24 | [{:rule-name rule-name 25 | :form '(if (some-func a b c) true false) 26 | :alt '(boolean (some-func a b c))}] 27 | "(if (some-func a b c) true false)" 28 | (single-rule-config rule-name)))) 29 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/cond_else.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.cond-else 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defn not-else [form] 12 | (not= :else form)) 13 | 14 | (defn not-else-other [form] 15 | (and (not-else form) 16 | (or (keyword? form) 17 | (true? form)))) 18 | 19 | (defrule style/cond-else 20 | "It's nice when the default branch is consistent. 21 | 22 | @examples 23 | 24 | ; avoid 25 | (cond 26 | (< 10 num) (println 10) 27 | (< 5 num) (println 5) 28 | true (println 0)) 29 | 30 | ; prefer 31 | (cond 32 | (< 10 num) (println 10) 33 | (< 5 num) (println 5) 34 | :else (println 0)) 35 | " 36 | {:pattern '(cond (?+ pairs not-else) (? _ not-else-other) ?else) 37 | :message "Use `:else` as the catch-all branch." 38 | :autocorrect true 39 | :replace '(cond ?pairs :else ?else)}) 40 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/missing_body_in_when_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.missing-body-in-when-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/missing-body-in-when) 13 | 14 | (defdescribe missing-body-in-when-test 15 | (it "handles symbols" 16 | (expect-match 17 | [{:rule-name 'lint/missing-body-in-when 18 | :form '(when true) 19 | :alt nil 20 | :message "Missing body in when"}] 21 | "(when true)" 22 | (single-rule-config rule-name))) 23 | (it "handles expressions" 24 | (expect-match 25 | [{:rule-name 'lint/missing-body-in-when 26 | :form '(when (some-func)) 27 | :alt nil 28 | :message "Missing body in when"}] 29 | "(when (some-func))" 30 | (single-rule-config rule-name)))) 31 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/not_some_pred_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.not-some-pred-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/not-some-pred) 13 | 14 | (defdescribe not-some-pred-test 15 | (it "works with symbols" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(not (some pred coll)) 19 | :alt '(not-any? pred coll)}] 20 | "(not (some pred coll))" 21 | (single-rule-config rule-name))) 22 | 23 | (it "works with non-symbols" 24 | (expect-match 25 | [{:rule-name rule-name 26 | :form '(not (some (splint/fn [%1] (even? (+ 1 %1))) coll)) 27 | :alt '(not-any? (splint/fn [%1] (even? (+ 1 %1))) coll)}] 28 | "(not (some #(even? (+ 1 %)) coll))" 29 | (single-rule-config rule-name)))) 30 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/prefer_clj_math_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.prefer-clj-math-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/prefer-clj-math) 13 | 14 | (defn config-with-version 15 | [minor] 16 | (assoc (single-rule-config rule-name) :clojure-version {:major 1 :minor minor})) 17 | 18 | (defdescribe prefer-clj-math-test 19 | (it "checks function calls" 20 | (expect-match 21 | '[{:alt clojure.math/atan}] 22 | "(Math/atan 45)" 23 | (config-with-version 12))) 24 | (it "checks bare symbols" 25 | (expect-match 26 | '[{:alt clojure.math/PI}] 27 | "Math/PI" 28 | (config-with-version 12))) 29 | 30 | (it "ignores if version is too low" 31 | (expect-match nil "(Math/atan 45)" 32 | (config-with-version 9)))) 33 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/naming/single_segment_namespace_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.naming.single-segment-namespace-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'naming/single-segment-namespace) 13 | 14 | (defdescribe single-segment-namespace-test 15 | (it "works on single segmets" (expect-match 16 | [{:rule-name rule-name 17 | :form '(ns simple) 18 | :message "simple is a single segment. Consider adding an additional segment." 19 | :alt nil}] 20 | "(ns simple)" 21 | (single-rule-config rule-name)) 22 | (expect-match nil "(ns foo.bar)" (single-rule-config rule-name))) 23 | 24 | (it "ignores special cases" 25 | (expect-match nil "(ns build)" (single-rule-config rule-name)) 26 | (expect-match nil "(ns user)" (single-rule-config rule-name)))) 27 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/assoc_assoc.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.assoc-assoc 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defrule style/assoc-assoc 12 | "Layering `assoc` calls are hard to read. `assoc-in` is known and idiomatic. 13 | 14 | @examples 15 | 16 | ; avoid 17 | (assoc coll :key1 (assoc (:key2 coll) :key2 new-val)) 18 | (assoc coll :key1 (assoc (coll :key2) :key2 new-val)) 19 | (assoc coll :key1 (assoc (get coll :key2) :key2 new-val)) 20 | 21 | ; prefer 22 | (assoc-in coll [:key1 :key2] new-val) 23 | " 24 | {:patterns ['(assoc ?coll ?key1 (assoc (?coll ?key1) ?key2 ?val)) 25 | '(assoc ?coll ?key1 (assoc (?key1 ?coll) ?key2 ?val)) 26 | '(assoc ?coll ?key1 (assoc (get ?coll ?key1) ?key2 ?val))] 27 | :message "Use `assoc-in` instead of recreating it." 28 | :autocorrect true 29 | :replace '(assoc-in ?coll [?key1 ?key2] ?val)}) 30 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/rand_int_one.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.rand-int-one 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defn low-num? [f] 13 | (when (number? f) 14 | (<= -1 f 1))) 15 | 16 | (defrule lint/rand-int-one 17 | "`clojure.core/rand-int` generates a float between `0` and `n` (exclusive) and then casts it to an integer. When given `1` (or a number less than `1`), `rand-int` will always return `0`. 18 | 19 | @examples 20 | 21 | ; avoid 22 | (rand-int 0) 23 | (rand-int -1) 24 | (rand-int 1) 25 | (rand-int 1.0) 26 | (rand-int -1.0) 27 | " 28 | {:pattern '(rand-int (? f low-num?)) 29 | :on-match (fn [ctx rule form {:syms [?f]}] 30 | (let [msg (format "Always returns 0. Did you mean (rand %s) or (rand-int 2)?" ?f)] 31 | (->diagnostic ctx rule form {:message msg})))}) 32 | -------------------------------------------------------------------------------- /corpus/arglists.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns arglists) 6 | 7 | (defn normal [a] a) 8 | 9 | (defn docstrings 10 | "This is a docstring" 11 | [a] a) 12 | 13 | (defn pre-attr-map 14 | {:arg 1} 15 | [a] a) 16 | 17 | (defn post-attr-map 18 | ([a] a) 19 | {:arg 1}) 20 | 21 | (defn rest-args 22 | [a b & c] 23 | (apply + a b c)) 24 | 25 | (defn rest-multiple-bodies 26 | ([a b] (+ a b)) 27 | ([a b & c] 28 | (apply + a b c))) 29 | 30 | (defn destructuring 31 | [{:keys [a b c]}] 32 | (+ a b c)) 33 | 34 | (defn wrapped-body ([a] a)) 35 | 36 | (defn multiple-bodies ([a] a) ([a b] (+ a b))) 37 | 38 | (defn multiple-bodies-docstrings 39 | "This is a docstring" 40 | ([a] a) 41 | ([a b] (+ a b))) 42 | 43 | (defn arglist-metadata 44 | {:arglists '([a] [a b] [a b c])} 45 | [& args] (apply + args)) 46 | 47 | #_:clj-kondo/ignore 48 | (defn error-bad-args (a) a) 49 | 50 | #_:clj-kondo/ignore 51 | (defn error-empty) 52 | 53 | #_:clj-kondo/ignore 54 | (defn error-bad-docstring 55 | [a] "asdf" a) 56 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/naming/conversion_functions_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.naming.conversion-functions-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'naming/conversion-functions) 13 | 14 | (defdescribe conversion-functions-test 15 | (it "expects single words" 16 | (expect-match 17 | [{:form '(defn f-to-c ...) 18 | :message "Use `->` instead of `to` in the names of conversion functions." 19 | :alt '(defn f->c ...)}] 20 | "(defn f-to-c [a] {:a a})" 21 | (single-rule-config rule-name))) 22 | (it "rejects multi-words" 23 | (expect-match 24 | nil 25 | "(defn expect-f-to-c [a] {:a a})" 26 | (single-rule-config rule-name)) 27 | (expect-match 28 | nil 29 | "(defn expect-f-to-c-something [a] {:a a})" 30 | (single-rule-config rule-name)))) 31 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/when_do_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.when-do-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/when-do) 13 | 14 | (defdescribe when-do-test 15 | (it "works with variable do args" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(when x (do y)) 19 | :alt '(when x y)}] 20 | "(when x (do y))" 21 | (single-rule-config rule-name)) 22 | (expect-match 23 | [{:form '(when x (do y z)) 24 | :alt '(when x y z)}] 25 | "(when x (do y z))" 26 | (single-rule-config rule-name))) 27 | (it "ignores if when has multiple args" 28 | (expect-match 29 | nil 30 | "(when x y (do z))" 31 | (single-rule-config rule-name)) 32 | (expect-match 33 | nil 34 | "(when x (do y) y)" 35 | (single-rule-config rule-name)))) 36 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/locking_object.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.locking-object 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]] 9 | [noahtheduke.splint.utils :refer [simple-type]])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (defn not-symbol? [obj] 14 | (not (symbol? obj))) 15 | 16 | (defrule lint/locking-object 17 | "Synchronizing on interned objects is really bad. If multiple places lock on the same type of interned objects, those places are competing for locks. 18 | 19 | @examples 20 | 21 | ; avoid 22 | (locking :hello (+ 1 1)) 23 | 24 | ; prefer 25 | (def hello (Object.)) 26 | (locking hello (+ 1 1)) 27 | " 28 | {:pattern '(locking (? obj not-symbol?) (?* _)) 29 | :on-match (fn [ctx rule form {:syms [?obj]}] 30 | (let [msg (str "Lock on a symbol bound to (Object.), not a " (name (simple-type ?obj)))] 31 | (->diagnostic ctx rule form {:message msg})))}) 32 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/dot_obj_method_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.dot-obj-method-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/dot-obj-method) 13 | 14 | (defdescribe dot-obj-usage-test 15 | (it "handles raw symbosl" 16 | (expect-match 17 | [{:rule-name 'lint/dot-obj-method 18 | :form '(. obj method 1 2 3) 19 | :message "Intention is clearer with `.method` form." 20 | :alt '(.method obj 1 2 3)}] 21 | "(. obj method 1 2 3)" 22 | (single-rule-config rule-name))) 23 | (it "handles namespaced symbosl" 24 | (expect-match 25 | [{:rule-name 'lint/prefer-method-values 26 | :alt '(CLASS/.method obj 1 2 3)}] 27 | "(. obj method 1 2 3)" 28 | (-> (single-rule-config rule-name) 29 | (assoc :clojure-version {:major 1 :minor 12}) 30 | (assoc-in ['lint/prefer-method-values :enabled] true))))) 31 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/redundant_regex_constructor_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.redundant-regex-constructor-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/redundant-regex-constructor) 13 | 14 | (defdescribe redundant-regex-constructor-test 15 | (it "works" 16 | (expect-match 17 | [{:message "Rely on regex literal directly." 18 | :alt '(splint/re-pattern "asdf")}] 19 | "(re-pattern #\"asdf\")" 20 | (single-rule-config rule-name))) 21 | (it "handles strings" 22 | (expect-match 23 | [{:alt '(splint/re-pattern "asdf")}] 24 | "(re-pattern \"asdf\")" 25 | (single-rule-config rule-name))) 26 | (it "compiles strings into regex" 27 | (expect-match 28 | [{:form '(re-pattern "\\asdf") 29 | :alt '(splint/re-pattern "\\asdf")}] 30 | "(re-pattern \"\\\\asdf\")" 31 | (single-rule-config rule-name)))) 32 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/reduce_str_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.reduce-str-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/reduce-str) 13 | 14 | (defdescribe reduce-str-test 15 | (it "works with no init-arg" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(reduce str x) 19 | :message "Use `clojure.string/join` for efficient string concatenation." 20 | :alt '(clojure.string/join x)}] 21 | "(reduce str x)" 22 | (single-rule-config rule-name))) 23 | (it "works with an empty init-arg" 24 | (expect-match 25 | [{:form '(reduce str "" x) 26 | :alt '(clojure.string/join x)}] 27 | "(reduce str \"\" x)" 28 | (single-rule-config rule-name))) 29 | (it "ignores a non-empty init-arg" 30 | (expect-match 31 | nil 32 | "(reduce str \"abc\" x)" 33 | (single-rule-config rule-name)))) 34 | -------------------------------------------------------------------------------- /dev/noahtheduke/splint/dev.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.dev 6 | (:require 7 | [noahtheduke.splint] 8 | [noahtheduke.splint.config :as config] 9 | [noahtheduke.splint.rules :refer [global-rules]])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (defn build-default-config [] 14 | (load-file "dev/noahtheduke/splint/rules/dev/throws_on_match.clj") 15 | (let [dev-rules (->> (keys (:rules @global-rules)) 16 | (filter #(.equals "dev" (namespace %))) 17 | (map (fn [r] (clojure.lang.MapEntry. r {:enabled true}))) 18 | (into {})) 19 | default-rules (config/read-default-config)] 20 | (merge dev-rules default-rules))) 21 | 22 | (def dev-config (atom (build-default-config))) 23 | 24 | (comment 25 | (do 26 | (require '[nextjournal.beholder :as beholder]) 27 | (def watcher 28 | (beholder/watch 29 | (fn [action] 30 | (when (#{:create :modify} (:type action)) 31 | (reset! dev-config (build-default-config)))) 32 | "resources")) 33 | 34 | (beholder/stop watcher))) 35 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/redundant_let.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.redundant-let 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defrule style/redundant-let 13 | "Directly nested lets can be merged into a single let block. 14 | 15 | @examples 16 | 17 | ; avoid 18 | (let [a 1] 19 | (let [b 2] 20 | (println a b))) 21 | 22 | (let [a 1 23 | b 2] 24 | (println a b)) 25 | " 26 | {:pattern '(let (? outer-bindings vector?) 27 | (let (? inner-bindings vector?) ?*body)) 28 | :message "Redundant let expressions can be merged." 29 | :autocorrect true 30 | :on-match (fn [ctx rule form {:syms [?outer-bindings ?inner-bindings ?body]}] 31 | (let [new-form (list* 'let 32 | (vec (concat ?outer-bindings ?inner-bindings)) 33 | ?body)] 34 | (->diagnostic ctx rule form {:replace-form new-form})))}) 35 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation and Aliases 2 | 3 | When used in a project as a library, put it in an alias to make it easier to invoke. 4 | 5 | ## Clojure CLI 6 | 7 | ```clojure 8 | :aliases {:splint {:extra-deps {io.github.noahtheduke/splint {:mvn/version "1.22.0"}} 9 | :main-opts ["-m" "noahtheduke.splint"]}} 10 | ``` 11 | 12 | Run with `clojure -M:splint [args...]`. 13 | 14 | ## Leiningen 15 | 16 | Add this to `project.clj`: 17 | 18 | ```clojure 19 | :profiles {:dev {:dependencies [[io.github.noahtheduke/splint "1.22.0"]]}} 20 | :aliases {"splint" ["run" "-m" "noahtheduke.splint"]} 21 | ``` 22 | 23 | Run with `lein splint [args...]`. 24 | 25 | ## Babashka 26 | 27 | Requires version 1.12.205 or later. If using `bb.edn`, add this to `bb.edn`: 28 | 29 | ```clojure 30 | :tasks {splint {:extra-deps {io.github.noahtheduke/splint {:mvn/version "1.22.0"}} 31 | :task noahtheduke.splint/-main}} 32 | ``` 33 | 34 | Run with `bb splint [args...]`. 35 | 36 | It can also be installed using `bbin`: 37 | 38 | ```text 39 | $ bbin install io.github.noahtheduke/splint 40 | {:coords 41 | #:git{:url "https://github.com/noahtheduke/splint", 42 | :tag "v1.22.0", 43 | :sha "..."}, 44 | :lib io.github.noahtheduke/splint} 45 | ``` 46 | 47 | Run with `splint [args...]`. 48 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/eq_zero_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.eq-zero-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it describe]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/eq-zero) 13 | 14 | (defdescribe eq-0-test 15 | (describe = 16 | (it "checks 0 first" 17 | (expect-match 18 | [{:rule-name rule-name 19 | :form '(= 0 x) 20 | :alt '(zero? x)}] 21 | "(= 0 x)" 22 | (single-rule-config rule-name))) 23 | 24 | (it "checks 0 second" 25 | (expect-match 26 | [{:alt '(zero? x)}] 27 | "(= x 0)" 28 | (single-rule-config rule-name)))) 29 | 30 | (describe == 31 | (it "checks 0 first" 32 | (expect-match 33 | [{:alt '(zero? x)}] 34 | "(== 0 x)" 35 | (single-rule-config rule-name))) 36 | 37 | (it "checks 0 second" 38 | (expect-match 39 | [{:alt '(zero? x)}] 40 | "(== x 0)" 41 | (single-rule-config rule-name))))) 42 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/filter_complement_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.filter-complement-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/filter-complement) 13 | 14 | (defdescribe filter-complement-test 15 | (it "complement" 16 | (expect-match 17 | [{:alt '(remove pred coll)}] 18 | "(filter (complement pred) coll)" 19 | (single-rule-config rule-name))) 20 | 21 | (it "anonymous literal" 22 | (expect-match 23 | [{:alt '(remove pred coll)}] 24 | "(filter #(not (pred %)) coll)" 25 | (single-rule-config rule-name))) 26 | 27 | (it "fn*" 28 | (expect-match 29 | [{:alt '(remove pred coll)}] 30 | "(filter (fn* [x] (not (pred x))) coll)" 31 | (single-rule-config rule-name))) 32 | 33 | (it "fn" 34 | (expect-match 35 | [{:alt '(remove pred coll)}] 36 | "(filter (fn [x] (not (pred x))) coll)" 37 | (single-rule-config rule-name)))) 38 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/prefixed_libspecs_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.prefixed-libspecs-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/prefixed-libspecs) 13 | 14 | (defdescribe prefixed-libspecs-test 15 | (it "handles (require calls)" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(quote [clojure [string :as str] [set :as set]]) 19 | :message "Don't use prefix libspecs in require calls"}] 20 | "(require 'clojure.pprint '[clojure.edn :as edn] '[clojure [string :as str] [set :as set]])" 21 | (single-rule-config rule-name))) 22 | (it "works" 23 | (expect-match 24 | [{:rule-name rule-name 25 | :form '[clojure [string :as str] [set :as set]] 26 | :message "Don't use prefix libspecs in require calls"}] 27 | "(ns foo.bar (:require clojure.pprint [clojure.edn :as edn] [clojure [string :as str] [set :as set]]))" 28 | (single-rule-config rule-name)))) 29 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/naming/predicate_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.naming.predicate-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'naming/predicate) 13 | 14 | (defdescribe predicate-test 15 | (it "checks for 'is-x'" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(defn is-palindrome [a] true) 19 | :alt '(defn palindrome? [a] true)}] 20 | "(defn is-palindrome [a] true)" 21 | (single-rule-config rule-name))) 22 | (it "checks for 'x-p'" 23 | (expect-match 24 | '[{:alt (defn palindrome? [a] true)}] 25 | "(defn palindrome-p [a] true)" 26 | (single-rule-config rule-name))) 27 | (it "ignores existing ?" 28 | (expect-match nil "(defn palindrome? [a] true)" (single-rule-config rule-name))) 29 | (it "trims leading is- when ? exists" 30 | (expect-match 31 | '[{:alt (defn palindrome? [a] true)}] 32 | "(defn is-palindrome? [a] true)" 33 | (single-rule-config rule-name)))) 34 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/underscore_in_namespace.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.underscore-in-namespace 6 | (:require 7 | [clojure.string :as str] 8 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 9 | [noahtheduke.splint.rules :refer [defrule]])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (defn includes-underscore? [?ns-sym] 14 | (str/includes? (str ?ns-sym) "_")) 15 | 16 | (defrule lint/underscore-in-namespace 17 | "Due to munging rules, underscores in namespaces can confuse tools and libraries which expect that underscores in class names should be dashes in Clojure. 18 | 19 | @examples 20 | 21 | ; avoid 22 | (ns foo_bar.baz_qux) 23 | 24 | ; prefer 25 | (ns foo-bar.baz-qux)" 26 | {:pattern '(ns (? ns-sym includes-underscore?) ?*_) 27 | :message "Avoid underscores in namespaces." 28 | :on-match (fn [ctx rule form {:syms [?ns-sym]}] 29 | (let [new-namespace (symbol (str/replace (str ?ns-sym) "_" "-"))] 30 | (->diagnostic ctx rule form {:replace-form new-namespace 31 | :form-meta (meta ?ns-sym)})))}) 32 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/warn_on_reflection.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.warn-on-reflection 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defrule lint/warn-on-reflection 13 | "Because we can't (or won't) check for interop, `*warn-on-reflection*` should 14 | be at the top of every file out of caution. 15 | 16 | @examples 17 | 18 | ; avoid 19 | (ns foo.bar) 20 | (defn baz [a b] (+ a b)) 21 | 22 | ; prefer 23 | (ns foo.bar) 24 | (set! *warn-on-reflection* true) 25 | (defn baz [a b] (+ a b)) 26 | " 27 | {:pattern '[(ns (?+ _)) ?warn (?* _)] 28 | :init-type :file 29 | :ext :clj 30 | :message "*warn-on-reflection* should be immediately after ns declaration." 31 | :on-match (fn [ctx rule form {:syms [?warn]}] 32 | (when-not (= '(set! *warn-on-reflection* true) ?warn) 33 | (->diagnostic ctx rule 34 | nil 35 | {:form-meta (meta ?warn) 36 | :filename (:filename (meta form))})))}) 37 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/performance/get_keyword_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.performance.get-keyword-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'performance/get-keyword) 13 | 14 | (defdescribe get-keyword-test 15 | (it "only looks for keywords" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(get m :some-key) 19 | :message "Use keywords as functions instead of the polymorphic function `get`." 20 | :alt '(:some-key m)}] 21 | "(get m :some-key)" 22 | (single-rule-config rule-name))) 23 | (it "ignores symbols" 24 | (expect-match 25 | nil 26 | "(get m 'some-key)" 27 | (single-rule-config rule-name))) 28 | (it "ignores strings" 29 | (expect-match 30 | nil 31 | "(get m \"some-key\")" 32 | (single-rule-config rule-name))) 33 | (it "ignores calls without 'get'" 34 | (expect-match 35 | nil 36 | "(m :some-key)" 37 | (single-rule-config rule-name)))) 38 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/assoc_fn.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.assoc-fn 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defn assoc? [sym] 12 | (and (symbol? sym) 13 | (.equals "assoc" (name sym)))) 14 | 15 | (defn not-assoc? [sym] 16 | (if (symbol? sym) 17 | (not (#{"assoc" "or"} (name sym))) 18 | true)) 19 | 20 | (defrule lint/assoc-fn 21 | "`assoc`-ing an update with the same key is hard to read. `update` is known and 22 | idiomatic. 23 | 24 | @examples 25 | 26 | ; avoid 27 | (assoc coll :a (+ (:a coll) 5)) 28 | (assoc coll :a (+ (coll :a) 5)) 29 | (assoc coll :a (+ (get coll :a) 5)) 30 | 31 | ; prefer 32 | (update coll :a + 5) 33 | " 34 | {:patterns ['((? _ assoc?) ?coll ?key ((? fn not-assoc?) (?key ?coll) ?*args)) 35 | '((? _ assoc?) ?coll ?key ((? fn not-assoc?) (?coll ?key) ?*args)) 36 | '((? _ assoc?) ?coll ?key ((? fn not-assoc?) (get ?coll ?key) ?*args))] 37 | :message "Use `update` instead of recreating it." 38 | :autocorrect true 39 | :replace '(update ?coll ?key ?fn ?args)}) 40 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/reduce_str.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.reduce-str 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]])) 8 | 9 | (set! *warn-on-reflection* true) 10 | 11 | (defn str-empty? [form] 12 | (.equals "" form)) 13 | 14 | (defrule style/reduce-str 15 | "`reduce` calls the provided function on every element in the provided 16 | collection. Because of how `str` is implemented, a new string is created 17 | every time it's called. Better to rely on `clojure.string/join`'s efficient 18 | StringBuilder and collection traversal. 19 | 20 | Additionally, the 2-arity form of `reduce` returns the first item without 21 | calling `str` on it if it only has one item total, which is 22 | generally not what is expected when calling `str` on something. 23 | 24 | @examples 25 | 26 | ; avoid 27 | (reduce str x) 28 | (reduce str \"\" x) 29 | 30 | ; prefer 31 | (clojure.string/join x) 32 | " 33 | {:pattern '(reduce str (?? _ str-empty?) ?coll) 34 | :message "Use `clojure.string/join` for efficient string concatenation." 35 | :autocorrect true 36 | :replace '(clojure.string/join ?coll)}) 37 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/if_else_nil.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.if-else-nil 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]] 9 | [noahtheduke.splint.rules.helpers :refer [rest-arg?]])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (defn not-do [form] 14 | (not (and (list? form) 15 | (= 'do (first form))))) 16 | 17 | (defrule lint/if-else-nil 18 | "Idiomatic `if` defines both branches. `when` returns `nil` in the else branch. 19 | 20 | @examples 21 | 22 | ; avoid 23 | (if (some-func) :a nil) 24 | 25 | ; prefer 26 | (when (some-func) :a) 27 | " 28 | {:patterns ['(if ?x (? y not-do) (?? _ nil?)) 29 | '(if ?x (do ?*y) (?? _ nil?))] 30 | :message "Use `when` which doesn't require specifying the else branch." 31 | :autocorrect true 32 | :on-match (fn [ctx rule form {:syms [?x ?y]}] 33 | (let [new-form (if (rest-arg? ?y) 34 | (list* 'when ?x ?y) 35 | (list 'when ?x ?y))] 36 | (->diagnostic ctx rule form {:replace-form new-form})))}) 37 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/performance/get_in_literals_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.performance.get-in-literals-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'performance/get-in-literals) 13 | 14 | (defdescribe get-in-literals-test 15 | (it "requires only keywords" 16 | (expect-match 17 | [{:rule-name 'performance/get-in-literals 18 | :form '(get-in m [:some-key1 :some-key2 :some-key3]) 19 | :message "Use keywords as functions instead of `get-in`." 20 | :alt '(-> m :some-key1 :some-key2 :some-key3)}] 21 | "(get-in m [:some-key1 :some-key2 :some-key3])" 22 | (single-rule-config rule-name)) 23 | (expect-match 24 | nil 25 | "(get-in m [:some-key1 :some-key2 'some-key3])" 26 | (single-rule-config rule-name)) 27 | (expect-match 28 | nil 29 | "(get-in m [:some-key1 some-key2 :some-key3])" 30 | (single-rule-config rule-name)) 31 | (expect-match 32 | nil 33 | "(get-in m [])" 34 | (single-rule-config rule-name)))) 35 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/multiple_arity_order_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.multiple-arity-order-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/multiple-arity-order) 13 | 14 | (defdescribe multiple-arity-order-test 15 | (it "works" 16 | (expect-match 17 | [{:form '(defn foo 18 | ([x] (foo x 1)) 19 | ([x y & more] (reduce foo (+ x y) more)) 20 | ([x y] (+ x y))) 21 | :message "defn arities should be sorted fewest to most arguments." 22 | :alt '(defn foo 23 | ([x] (foo x 1)) 24 | ([x y] (+ x y)) 25 | ([x y & more] (reduce foo (+ x y) more)))}] 26 | "(defn foo 27 | ([x] (foo x 1)) 28 | ([x y & more] (reduce foo (+ x y) more)) 29 | ([x y] (+ x y)))" 30 | (single-rule-config rule-name))) 31 | (it "ignores incorrect defns" 32 | (expect-match 33 | nil 34 | "(defn foo ([a] 1) [a b])" 35 | (single-rule-config rule-name)))) 36 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/fn_wrapper_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.fn-wrapper-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/fn-wrapper) 13 | 14 | (defdescribe fn-wrapper-test 15 | (it "handles various anonymous functions" 16 | (expect-match '[{:alt f}] "(fn* [arg] (f arg))" (single-rule-config rule-name)) 17 | (expect-match '[{:alt f}] "(fn [arg] (f arg))" (single-rule-config rule-name)) 18 | (expect-match '[{:alt f}] "#(f %)" (single-rule-config rule-name))) 19 | 20 | (it "ignores interop functions" 21 | (expect-match nil "#(Integer/parseInt %)" (single-rule-config rule-name)) 22 | (expect-match nil "(do (import (java.util.regex Pattern)) #(Pattern/compile %))" (single-rule-config rule-name)) 23 | (expect-match nil "#(.getPath %)" (single-rule-config rule-name))) 24 | (it "handles fns to skip" 25 | (expect-match nil "(ns foo (:require [dev.nu.morse :as morse])) (add-tap (fn [x] (morse/inspect x)))" 26 | (single-rule-config rule-name {:names-to-skip #{'inspect}})))) 27 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/single_key_in.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.single-key-in 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def multi->single 13 | '{assoc-in assoc 14 | get-in get 15 | update-in update}) 16 | 17 | (defrule style/single-key-in 18 | "`assoc-in` loops over the args, calling `assoc` for each key. If given a single key, 19 | just call `assoc` directly instead for performance and readability improvements. 20 | 21 | @examples 22 | 23 | ; avoid 24 | (assoc-in coll [:k] 10) 25 | 26 | ; prefer 27 | (assoc coll :k 10) 28 | " 29 | {:pattern '((? f multi->single) ?coll [?key] ?*vals) 30 | :autocorrect true 31 | :on-match (fn [ctx rule form {:syms [?f ?coll ?key ?vals]}] 32 | (let [f (multi->single ?f) 33 | new-form (list* f ?coll ?key ?vals) 34 | message (format "Use `%s` instead of recreating it." f)] 35 | (->diagnostic ctx rule form {:replace-form new-form 36 | :message message})))}) 37 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/naming/single_segment_namespace.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.naming.single-segment-namespace 6 | (:require 7 | [clojure.string :as str] 8 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 9 | [noahtheduke.splint.rules :refer [defrule]])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (defn single-segment-ns? [sexp] 14 | (let [ns-str (str sexp)] 15 | (not (or (str/includes? ns-str ".") 16 | (#{"build" "user"} (str sexp)))))) 17 | 18 | (defrule naming/single-segment-namespace 19 | "Namespaces exist to disambiguate names. Using a single segment namespace puts you in direct conflict with everyone else using single segment namespaces, thus making it more likely you will conflict with another code base. 20 | 21 | @examples 22 | 23 | ; avoid 24 | (ns simple) 25 | 26 | ; prefer 27 | (ns noahtheduke.simple) 28 | " 29 | {:pattern '(ns (? ns single-segment-ns?) ?*args) 30 | :on-match (fn [ctx rule form {:syms [?ns]}] 31 | (let [message 32 | (format "%s is a single segment. Consider adding an additional segment." ?ns)] 33 | (->diagnostic ctx rule form {:message message})))}) 34 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/min_max.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.min-max 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defrule lint/min-max 13 | "Clamping a value between two numbers requires saying at max of the lower number and a min of the higher number. If the min is lower than the max, then the min 14 | 15 | @examples 16 | 17 | ; avoid 18 | (min 10 (max 100 foo)) 19 | (max 100 (min 10 foo)) 20 | 21 | ; prefer 22 | (min 100 (max 10 foo)) 23 | " 24 | {:patterns ['(min (? min number?) (max (? max number?) ?v)) 25 | '(max (? max number?) (min (? min number?) ?v))] 26 | :on-match (fn [ctx rule form {:syms [?min ?max ?v]}] 27 | (when (<= ?min ?max) 28 | (let [new-form (when (not= ?min ?max) 29 | (list 'min ?max (list 'max ?min ?v))) 30 | message (format "Incorrect clamping, will always be %s." ?min)] 31 | (->diagnostic ctx rule form {:replace-form new-form 32 | :message message}))))}) 33 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/no_target_for_method_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.no-target-for-method-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/no-target-for-method) 13 | 14 | (defdescribe no-target-for-method-test 15 | (it "works" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(.length) 19 | :message "Instance methods require a target instance." 20 | :alt nil}] 21 | "(.length)" 22 | (single-rule-config rule-name)) 23 | (expect-match 24 | [{:rule-name rule-name 25 | :form '(String/.length) 26 | :message "Instance methods require a target instance." 27 | :alt nil}] 28 | "(String/.length)" 29 | (single-rule-config rule-name))) 30 | (it "ignores nested calls" 31 | (expect-match 32 | nil 33 | "(doto (new java.util.HashMap) (.put \"a\" 1) (.put \"b\" 2))" 34 | (single-rule-config rule-name))) 35 | (it "ignores non-interop calls" 36 | (expect-match 37 | nil 38 | "(foo)" 39 | (single-rule-config rule-name)))) 40 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/warn_on_reflection_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.warn-on-reflection-test 6 | (:require 7 | [clojure.java.io :as io] 8 | [lazytest.core :refer [defdescribe it]] 9 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (def rule-name 'lint/warn-on-reflection) 14 | 15 | (defdescribe warn-on-reflection-test 16 | (it "works" 17 | (expect-match 18 | [{:rule-name 'lint/warn-on-reflection 19 | :form nil 20 | :message "*warn-on-reflection* should be immediately after ns declaration." 21 | :alt nil 22 | :line 7 23 | :column 1 24 | :end-line 7 25 | :end-column 20 26 | :filename (io/file "corpus" "arglists.clj")}] 27 | (io/file "corpus" "arglists.clj") 28 | (single-rule-config rule-name)) 29 | (expect-match 30 | [{:rule-name 'lint/warn-on-reflection}] 31 | "(ns example) (+ 1 1)" 32 | (single-rule-config rule-name))) 33 | (it "ignores when no namespaces" 34 | (expect-match nil "(+ 1 1)" (single-rule-config rule-name))) 35 | (it "ignores when namespace is broken" 36 | (expect-match nil "(ns) (+ 1 1)" (single-rule-config rule-name)))) 37 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/let_when_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.let-when-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/let-when) 13 | 14 | (defdescribe let-when-test 15 | (it "works with when" 16 | (expect-match 17 | [{:rule-name 'lint/let-when 18 | :form '(let [result (some-func)] (when result (do-stuff result))) 19 | :alt '(when-let [result (some-func)] (do-stuff result))}] 20 | "(let [result (some-func)] (when result (do-stuff result)))" 21 | (single-rule-config rule-name))) 22 | (it "works with ifs with one branch" 23 | (expect-match 24 | [{:rule-name 'lint/let-when 25 | :form '(let [result (some-func)] (if result (do-stuff result))) 26 | :alt '(when-let [result (some-func)] (do-stuff result))}] 27 | "(let [result (some-func)] (if result (do-stuff result)))" 28 | (single-rule-config rule-name))) 29 | (it "ignores ifs with 2 branches" 30 | (expect-match 31 | nil 32 | "(let [result (some-func)] (if result (do-stuff result) (some-func)))" 33 | (single-rule-config rule-name)))) 34 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/useless_do.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.useless-do 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]] 9 | [noahtheduke.splint.rules.helpers :refer [unquote-splicing??]])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (defn not-unquote-splicing [sexp] 14 | (not (when (sequential? sexp) 15 | (unquote-splicing?? (first sexp))))) 16 | 17 | (defrule style/useless-do 18 | "A single item in a `do` is a no-op. However, it is sometimes necessary to wrap expressions in `do`s to avoid issues, so `do` surrounding `~@something` will be skipped as well as `#(do something)`. 19 | 20 | @examples 21 | 22 | ; avoid 23 | (do coll) 24 | 25 | ; prefer 26 | coll 27 | 28 | ; skipped 29 | (do ~@body) 30 | #(do [%1 %2]) 31 | " 32 | {:pattern '(do (? x not-unquote-splicing)) 33 | :message "Unnecessary `do`." 34 | :autocorrect true 35 | :on-match (fn [ctx rule form {:syms [?x]}] 36 | (let [parent-form (:parent-form ctx)] 37 | (when-not (and (sequential? parent-form) 38 | (= 'splint/fn (first parent-form))) 39 | (->diagnostic ctx rule form {:replace-form ?x}))))}) 40 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/assoc_assoc_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.assoc-assoc-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/assoc-assoc) 13 | 14 | (defdescribe assoc-assoc-key-coll-test 15 | (it "respects keyword first" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(assoc coll :k1 (assoc (:k1 coll) :k2 v)) 19 | :alt '(assoc-in coll [:k1 :k2] v)}] 20 | "(assoc coll :k1 (assoc (:k1 coll) :k2 v))" 21 | (single-rule-config rule-name))) 22 | 23 | (it "respects nested coll-first" 24 | (expect-match 25 | [{:rule-name rule-name 26 | :form '(assoc coll :k1 (assoc (coll :k1) :k2 v)) 27 | :alt '(assoc-in coll [:k1 :k2] v)}] 28 | "(assoc coll :k1 (assoc (coll :k1) :k2 v))" 29 | (single-rule-config rule-name))) 30 | 31 | (it "respects nested get" 32 | (expect-match 33 | [{:rule-name rule-name 34 | :form '(assoc coll :k1 (assoc (get coll :k1) :k2 v)) 35 | :alt '(assoc-in coll [:k1 :k2] v)}] 36 | "(assoc coll :k1 (assoc (get coll :k1) :k2 v))" 37 | (single-rule-config rule-name)))) 38 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/naming/record_name.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.naming.record-name 6 | (:require 7 | [camel-snake-kebab.core :as csk] 8 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 9 | [noahtheduke.splint.rules :refer [defrule]])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (defn bad-name? [sexp] 14 | (when (symbol? sexp) 15 | (let [record-name (str sexp)] 16 | (not= record-name (csk/->PascalCase record-name))))) 17 | 18 | (defrule naming/record-name 19 | "Records should use PascalCase. (Replacement is generated with [camel-snake-kebab](https://github.com/clj-commons/camel-snake-kebab).) 20 | 21 | @examples 22 | 23 | ; avoid 24 | (defrecord foo [a b c]) 25 | (defrecord foo-bar [a b c]) 26 | (defrecord Foo-bar [a b c]) 27 | 28 | ; prefer 29 | (defrecord Foo [a b c]) 30 | (defrecord FooBar [a b c]) 31 | " 32 | {:pattern '(defrecord (? record-name bad-name?) ?*args) 33 | :message "Records should use PascalCase." 34 | :on-match (fn [ctx rule form {:syms [?record-name ?args]}] 35 | (let [new-record-name (symbol (csk/->PascalCase ?record-name)) 36 | new-form (list* 'defrecord new-record-name ?args)] 37 | (->diagnostic ctx rule form {:replace-form new-form})))}) 38 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/if_else_nil_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.if-else-nil-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/if-else-nil) 13 | 14 | (defdescribe if-else-nil-test 15 | (it "works on 2 arity" 16 | (expect-match 17 | [{:form '(if x y) 18 | :message "Use `when` which doesn't require specifying the else branch." 19 | :alt '(when x y)}] 20 | "(if x y)" 21 | (single-rule-config rule-name))) 22 | (it "respects else nils" 23 | (expect-match 24 | [{:form '(if x y nil) 25 | :alt '(when x y)}] 26 | "(if x y nil)" 27 | (single-rule-config rule-name))) 28 | (it "handles `do`" 29 | (expect-match 30 | [{:form '(if x (do y)) 31 | :alt '(when x y)}] 32 | "(if x (do y))" 33 | (single-rule-config rule-name))) 34 | (it "ignores non-nil 3 arity" 35 | (expect-match nil "(if x y z)" (single-rule-config rule-name))) 36 | 37 | (it "respects nested ifs" 38 | (expect-match 39 | '[{:alt (when x (if y (z a b c) d))}] 40 | "(if x (if y (z a b c) d))" 41 | (single-rule-config rule-name)))) 42 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/set_literal_as_fn_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.set-literal-as-fn-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/set-literal-as-fn) 13 | 14 | (defdescribe set-literal-as-fn-test 15 | (it "plain strings" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(#{'a 'b 'c} elem) 19 | :message "Prefer `case` to set literal with constant members." 20 | :alt '(case elem (a b c) elem nil)}] 21 | "(#{'a 'b 'c} elem)" 22 | (single-rule-config rule-name))) 23 | (it "other constant types" 24 | (expect-match 25 | [{:rule-name rule-name 26 | :form '(#{nil 1 :b 'c} elem) 27 | :message "Prefer `case` to set literal with constant members." 28 | :alt '(case elem (nil 1 :b c) elem nil)}] 29 | "(#{nil 1 :b 'c} elem)" 30 | (single-rule-config rule-name))) 31 | (it "ignores if not all elements are quoted" 32 | (expect-match nil "(#{'a 'b c} elem)" (single-rule-config rule-name))) 33 | (it "ignores if element can't be treated as constant" 34 | (expect-match nil "(#{'a 'b 'c '(1 2 3)} elem)" (single-rule-config rule-name)))) 35 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/is_eq_order.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.is-eq-order 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]] 8 | [noahtheduke.splint.rules.helpers :refer [simple-literal?]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defn call-or-sym? [form] 13 | (or (symbol? form) 14 | (and (list? form) 15 | (not (#{'quote 'syntax-quote 'splint/syntax-quote} (first form)))))) 16 | 17 | (defrule style/is-eq-order 18 | "`clojure.test/is` expects `=`-based assertions to put the expected value first. 19 | 20 | This rule uses two checks on the `=` call to determine if it should issue a diagnostic: 21 | * Is the first argument a symbol or an unquoted list? (A variable/local or a call.) 22 | * Is the second argument a nil, boolean, char, number, keyword, or string? 23 | 24 | @examples 25 | 26 | ; avoid 27 | (is (= status 200)) 28 | (is (= (my-plus 1 2) 3)) 29 | 30 | ; prefer 31 | (is (= 200 status)) 32 | (is (= 3 (my-plus 1 2))) 33 | 34 | ; non-issues 35 | (is (= (hash-map :a 1) {:a 1})) 36 | (is (= (hash-set :a 1) #{:a 1})) 37 | " 38 | {:pattern '(is (= (? actual call-or-sym?) (? expected simple-literal?)) ??msg) 39 | :message "Expected value should go first" 40 | :autocorrect true 41 | :replace '(is (= ?expected ?actual) ?msg)}) 42 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/prefer_condp_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.prefer-condp-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/prefer-condp) 13 | 14 | (defdescribe prefer-condp-test 15 | (it "works with no default case" 16 | (expect-match 17 | '[{:alt (condp = x 1 :one 2 :two 3 :three 4 :four)}] 18 | "(cond (= 1 x) :one (= 2 x) :two (= 3 x) :three (= 4 x) :four)" 19 | (single-rule-config rule-name))) 20 | (it "works with a default case" 21 | (expect-match 22 | '[{:alt (condp = x 1 :one 2 :two 3 :three :big)}] 23 | "(cond (= 1 x) :one (= 2 x) :two (= 3 x) :three :else :big)" 24 | (single-rule-config rule-name))) 25 | (it "can handle complex test cases" 26 | (expect-match 27 | '[{:alt (condp apply [2 3] = "eq" < "lt" > "gt")}] 28 | "(cond (apply = [2 3]) \"eq\" (apply < [2 3]) \"lt\" (apply > [2 3]) \"gt\")" 29 | (single-rule-config rule-name))) 30 | (it "ignores built-in macros" 31 | (expect-match nil "(cond (and a b) true (and c b) false)" (single-rule-config rule-name)) 32 | (expect-match nil "(cond (or a b) true (or c b) false)" (single-rule-config rule-name)))) 33 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/not_empty.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.not-empty 6 | (:require 7 | [noahtheduke.splint.rules :refer [defrule]] 8 | [noahtheduke.splint.diagnostic :refer [->diagnostic]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defn seq-diagnostic [ctx rule form {:syms [?x]}] 13 | (->diagnostic ctx rule form 14 | {:message "`seq` is idiomatic, gotta learn to love it." 15 | :replace-form (list 'seq ?x)})) 16 | 17 | (defn not-empty-diagnostic [ctx rule form {:syms [?x]}] 18 | (->diagnostic ctx rule form 19 | {:message "`not-empty` is built-in." 20 | :replace-form (list 'not-empty ?x)})) 21 | 22 | (defrule lint/not-empty? 23 | "`seq` returns `nil` when given an empty collection. `empty?` is implemented as `(not (seq coll))` so it's idiomatic to use `seq` directly. 24 | 25 | @examples 26 | 27 | ; avoid 28 | (not (empty? coll)) 29 | 30 | ; prefer (chosen style :seq (default)) 31 | (seq coll) 32 | 33 | ; prefer (chosen style :not-empty) 34 | (not-empty coll) 35 | " 36 | {:pattern '(not (empty? ?x)) 37 | :autocorrect true 38 | :on-match (fn [ctx rule form bindings] 39 | (condp = (:chosen-style (:config rule)) 40 | :seq (seq-diagnostic ctx rule form bindings) 41 | :not-empty (not-empty-diagnostic ctx rule form bindings)))}) 42 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/def_fn_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.def-fn-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/def-fn) 13 | 14 | (defdescribe def-let-fn-test 15 | (it "finds fn in let in def" 16 | (expect-match 17 | [{:form '(def check-inclusion 18 | (let [allowed #{:a :b :c}] 19 | (fn [i] (contains? allowed i)))) 20 | :message "Prefer `let` wrapping `defn`." 21 | :alt '(let [allowed #{:a :b :c}] 22 | (defn check-inclusion [i] 23 | (contains? allowed i)))}] 24 | "(def check-inclusion (let [allowed #{:a :b :c}] (fn [i] (contains? allowed i))))" 25 | (single-rule-config rule-name))) 26 | 27 | (it "finds fn in def" 28 | (expect-match 29 | [{:form '(def some-func (fn [i] (+ i 100))) 30 | :message "Prefer `defn` instead of `def` wrapping `fn`." 31 | :alt '(defn some-func [i] (+ i 100))}] 32 | "(def some-func (fn [i] (+ i 100)))" 33 | (single-rule-config rule-name))) 34 | (it "finds fn in def" 35 | (expect-match 36 | nil 37 | "`(def some-func (fn [i] (+ i 100)))" 38 | (single-rule-config rule-name)))) 39 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/redundant_regex_constructor.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.redundant-regex-constructor 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]] 9 | [noahtheduke.splint.pattern :refer [pattern]])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (def regex-pat? (pattern '(splint/re-pattern ?reg-str))) 14 | 15 | (defrule style/redundant-regex-constructor 16 | "Clojure regex literals (#\"\") are passed to `java.util.regex.Pattern/compile` at read time. `re-pattern` checks if the given arg is a Pattern, making it a no-op when given a regex literal. 17 | 18 | @examples 19 | 20 | ; avoid 21 | (re-pattern #\".*\") 22 | 23 | ; prefer 24 | #\".*\" 25 | " 26 | {:pattern '(re-pattern ?pat) 27 | :message "Rely on regex literal directly." 28 | :autocorrect true 29 | :on-match (fn [ctx rule form {:syms [?pat]}] 30 | (if (string? ?pat) 31 | (let [new-form (list 'splint/re-pattern (str (re-pattern ?pat)))] 32 | (->diagnostic ctx rule form {:replace-form new-form})) 33 | (when-let [{:syms [?reg-str]} (regex-pat? ?pat)] 34 | (let [new-form (list 'splint/re-pattern ?reg-str)] 35 | (->diagnostic ctx rule form {:replace-form new-form})))))}) 36 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/rand_int_one_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.rand-int-one-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/rand-int-one) 13 | 14 | (defdescribe rand-int-one-test 15 | (it "works on integers" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(rand-int 1) 19 | :message "Always returns 0. Did you mean (rand 1) or (rand-int 2)?" 20 | :alt nil}] 21 | "(rand-int 1)" 22 | (single-rule-config rule-name)) 23 | (expect-match 24 | [{:rule-name rule-name 25 | :form '(rand-int -1) 26 | :message "Always returns 0. Did you mean (rand -1) or (rand-int 2)?" 27 | :alt nil}] 28 | "(rand-int -1)" 29 | (single-rule-config rule-name))) 30 | (it "works on floats" 31 | (expect-match 32 | [{:rule-name rule-name 33 | :form '(rand-int 1.0) 34 | :message "Always returns 0. Did you mean (rand 1.0) or (rand-int 2)?" 35 | :alt nil}] 36 | "(rand-int 1.0)" 37 | (single-rule-config rule-name))) 38 | (it "doesn't run on larger numbers" 39 | (expect-match 40 | nil 41 | "(rand-int 1.5)" 42 | (single-rule-config rule-name)))) 43 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/prefer_method_values_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.prefer-method-values-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defn config [] 13 | (-> (single-rule-config 'lint/prefer-method-values) 14 | (assoc-in ['style/prefer-clj-string :enabled] true) 15 | (assoc :clojure-version {:major 1 :minor 12}))) 16 | 17 | (defdescribe prefer-method-values-test 18 | (it "respects dot method" 19 | (expect-match 20 | [{:rule-name 'lint/prefer-method-values 21 | :form '(.bar foo baz) 22 | :message "Prefer uniform Class/member syntax instead of traditional interop." 23 | :alt '(CLASS/.bar foo baz)}] 24 | "(.bar foo baz)" 25 | (config))) 26 | (it "respects period method" 27 | (expect-match 28 | [{:rule-name 'lint/prefer-method-values 29 | :form '(. Object (method) 1 2 3) 30 | :message "Prefer uniform Class/member syntax instead of traditional interop." 31 | :alt '(Object/method 1 2 3)}] 32 | "(ns foo (:import (java.lang Object))) (. Object (method) 1 2 3)" 33 | (config))) 34 | (it "doesn't work with lower clojure version" 35 | (expect-match 36 | nil 37 | "(.bar foo baz)" 38 | (assoc (config) :clojure-version {:major 1 :minor 11})))) 39 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/style/single_key_in_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.style.single-key-in-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'style/single-key-in) 13 | 14 | (defdescribe single-key-in-test 15 | (it assoc-in 16 | (expect-match 17 | '[{:form (assoc-in coll [:k] v) 18 | :message "Use `assoc` instead of recreating it." 19 | :alt (assoc coll :k v)}] 20 | "(assoc-in coll [:k] v)" 21 | (single-rule-config rule-name))) 22 | (it get-in 23 | (expect-match 24 | '[{:alt (get coll :k) 25 | :message "Use `get` instead of recreating it."}] 26 | "(get-in coll [:k])" 27 | (single-rule-config rule-name))) 28 | (it "get-in with default" 29 | (expect-match 30 | '[{:alt (get coll :k :default)}] 31 | "(get-in coll [:k] :default)" 32 | (single-rule-config rule-name))) 33 | (it update-in 34 | (expect-match 35 | '[{:alt (update coll :k inc) 36 | :message "Use `update` instead of recreating it."}] 37 | "(update-in coll [:k] inc)" 38 | (single-rule-config rule-name))) 39 | (it "update-in with varargs" 40 | (expect-match 41 | '[{:alt (update coll :k + 1 2 3)}] 42 | "(update-in coll [:k] + 1 2 3)" 43 | (single-rule-config rule-name)))) 44 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/prefer_require_over_use_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.prefer-require-over-use-test 6 | (:require 7 | [lazytest.core :refer [defdescribe describe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defn config [& [extra]] 13 | (merge (single-rule-config 'lint/prefer-require-over-use) extra)) 14 | 15 | (defdescribe prefer-require-over-use-test 16 | (describe "chosen styles" 17 | (it ":as" 18 | (expect-match 19 | [{:rule-name 'lint/prefer-require-over-use 20 | :form '(ns examples.ns (:use clojure.zip)) 21 | :alt nil 22 | :message "Use (:require [some.lib :as l]) over (:use some.lib)"}] 23 | "(ns examples.ns (:use clojure.zip))" 24 | (config {'lint/prefer-require-over-use {:chosen-style :as}}))) 25 | (it ":refer" 26 | (expect-match 27 | [{:alt nil 28 | :message "Use (:require [some.lib :refer [...]]) over (:use some.lib)"}] 29 | "(ns examples.ns (:use clojure.zip))" 30 | (config {'lint/prefer-require-over-use {:chosen-style :refer}}))) 31 | (it ":all" 32 | (expect-match 33 | [{:alt nil 34 | :message "Use (:require [some.lib :refer :all]) over (:use some.lib)"}] 35 | "(ns examples.ns (:use clojure.zip))" 36 | (config {'lint/prefer-require-over-use {:chosen-style :all}}))))) 37 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/min_max_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.min-max-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/min-max) 13 | 14 | (defdescribe min-max-test 15 | (it "works if min is the outer call" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(min 0 (max 500 foo)) 19 | :message "Incorrect clamping, will always be 0." 20 | :alt '(min 500 (max 0 foo))}] 21 | "(min 0 (max 500 foo))" 22 | (single-rule-config rule-name))) 23 | (it "works if max is the outer call" 24 | (expect-match 25 | [{:rule-name rule-name 26 | :form '(max 500 (min 0 foo)) 27 | :message "Incorrect clamping, will always be 0." 28 | :alt '(min 500 (max 0 foo))}] 29 | "(max 500 (min 0 foo))" 30 | (single-rule-config rule-name))) 31 | (it "Checks if the numbers are the same" 32 | (expect-match 33 | [{:rule-name rule-name 34 | :form '(min 10 (max 10 foo)) 35 | :message "Incorrect clamping, will always be 10." 36 | :alt nil}] 37 | "(min 10 (max 10 foo))" 38 | (single-rule-config rule-name))) 39 | (it "Ignores if written correctly" 40 | (expect-match 41 | nil 42 | "(min 100 (max 10 foo))" 43 | (single-rule-config rule-name)))) 44 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/redundant_nested_call.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.redundant-nested-call 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defrule style/redundant-nested-call 13 | "Some clojure.core functions and macros take a variable number of args, so there's no need to nest calls. To check non-clojure.core functions, they can be added to the config under key `:fn-names`: `style/redundant-nested-call {:fn-names [foo]}`. 14 | 15 | > [!NOTE] 16 | > This can have performance implications in certain hot-loops. 17 | 18 | @examples 19 | 20 | ; avoid 21 | (+ 1 2 (+ 3 4)) 22 | (comp :foo :bar (comp :qux :ply)) 23 | 24 | ; prefer 25 | (+ 1 2 3 4) 26 | (comp :foo :bar :qux :ply) 27 | 28 | ; with `:fn-names [foo]` 29 | ; avoid 30 | (foo 1 2 (foo 3 4)) 31 | 32 | ; prefer 33 | (foo 1 2 3 4) 34 | " 35 | {:pattern '((? fun symbol?) ?+args (?fun ?+others)) 36 | :on-match (fn [ctx {{:keys [fn-names]} :config :as rule} form {:syms [?fun ?args ?others]}] 37 | (when (contains? fn-names (symbol (name ?fun))) 38 | (let [new-form (list* ?fun (concat ?args ?others))] 39 | (->diagnostic ctx rule form {:message (format "Redundant nested call: `%s`." ?fun) 40 | :replace-form new-form}))))}) 41 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/lint/no_op_assignment_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.lint.no-op-assignment-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'lint/no-op-assignment) 13 | 14 | (defdescribe no-op-assignment-test 15 | (it "handles the trivial case" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(foo foo) 19 | :message "Avoid no-op assignment." 20 | :alt nil}] 21 | "(let [foo foo] bar)" 22 | (single-rule-config rule-name))) 23 | (it "checks multiple cases at once" 24 | (expect-match 25 | [{:rule-name rule-name 26 | :form '(bar bar) 27 | :message "Avoid no-op assignment." 28 | :alt nil} 29 | {:rule-name rule-name 30 | :form '(foo foo) 31 | :message "Avoid no-op assignment." 32 | :alt nil}] 33 | "(let [foo 1 foo foo bar foo bar bar] bar)" 34 | (single-rule-config rule-name))) 35 | (it "ignores when in a reader conditional" 36 | (expect-match 37 | nil 38 | "(let [remote #?(:clj remote :cljs (foo bar))])" 39 | (single-rule-config rule-name))) 40 | (it "ignores when the right side has a type-hint" 41 | (expect-match 42 | nil 43 | "(let [remote ^ExceptionInfo remote] remote)" 44 | (single-rule-config rule-name)))) 45 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/path_matcher.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.path-matcher 6 | (:require 7 | [clojure.string :as str]) 8 | (:import 9 | (java.io File) 10 | (java.nio.file FileSystem FileSystems PathMatcher))) 11 | 12 | (set! *warn-on-reflection* true) 13 | 14 | (defonce ^FileSystem fs (FileSystems/getDefault)) 15 | 16 | (defprotocol Match 17 | (-matches [matcher file-or-path] "Check if file-or-path matches pattern.")) 18 | 19 | (defrecord MatchHolder [pattern input]) 20 | 21 | (extend-protocol Match 22 | PathMatcher 23 | (-matches [m file] 24 | (.matches m (.toPath ^File file))) 25 | java.util.regex.Pattern 26 | (-matches [m file] 27 | (let [m (re-matcher m (str file))] 28 | (.find m))) 29 | String 30 | (-matches [m file] 31 | (str/includes? (str file) m))) 32 | 33 | (defn matches [matcher file] 34 | (-matches (:pattern matcher) file)) 35 | 36 | (defn re-find-matcher [input] 37 | (re-pattern (str/replace-first input "re-find:" ""))) 38 | 39 | (defn ->matcher [input] 40 | (cond 41 | (or (str/starts-with? input "glob:") 42 | (str/starts-with? input "regex:")) 43 | (->MatchHolder (.getPathMatcher fs input) input) 44 | (str/starts-with? input "re-find:") 45 | (->MatchHolder (re-find-matcher input) input) 46 | (str/starts-with? input "string:") 47 | (->MatchHolder (str/replace-first input "string:" "") input) 48 | :else 49 | (->MatchHolder (re-find-matcher input) (str "re-find:" input)))) 50 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/rules/naming/record_name_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.rules.naming.record-name-test 6 | (:require 7 | [lazytest.core :refer [defdescribe it]] 8 | [noahtheduke.splint.test-helpers :refer [expect-match single-rule-config]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (def rule-name 'naming/record-name) 13 | 14 | (defdescribe record-name-test 15 | (it "handles all cases" 16 | (expect-match 17 | [{:rule-name rule-name 18 | :form '(defrecord foo [a b c]) 19 | :alt '(defrecord Foo [a b c])}] 20 | "(defrecord foo [a b c])" 21 | (single-rule-config rule-name)) 22 | (expect-match 23 | [{:rule-name rule-name 24 | :form '(defrecord fooBar [a b c]) 25 | :alt '(defrecord FooBar [a b c])}] 26 | "(defrecord fooBar [a b c])" 27 | (single-rule-config rule-name)) 28 | (expect-match 29 | [{:rule-name rule-name 30 | :form '(defrecord foo-bar [a b c]) 31 | :alt '(defrecord FooBar [a b c])}] 32 | "(defrecord foo-bar [a b c])" 33 | (single-rule-config rule-name)) 34 | (expect-match 35 | [{:rule-name rule-name 36 | :form '(defrecord Foo-bar [a b c]) 37 | :alt '(defrecord FooBar [a b c])}] 38 | "(defrecord Foo-bar [a b c])" 39 | (single-rule-config rule-name))) 40 | (it "doesn't crash in a macro" 41 | (expect-match 42 | nil 43 | "`(defrecord ~@body)" 44 | (single-rule-config rule-name)))) 45 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/diagnostic.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.diagnostic 6 | "Namespace for all Diagnostics-related functionality. 7 | 8 | A Diagnostic is an instance of a match of a rule's pattern in a given 9 | analyzed code. It has the following definition: 10 | 11 | ```clojure 12 | (defrecord Diagnostic 13 | [rule-name form message alt line column end-line end-column filename exception]) 14 | ```") 15 | 16 | (set! *warn-on-reflection* true) 17 | 18 | (defrecord Diagnostic [rule-name form message alt line column end-line end-column filename exception]) 19 | 20 | (defn ->diagnostic 21 | "Create and return a new diagnostic." 22 | ([ctx rule form] (->diagnostic ctx rule form nil)) 23 | ([ctx rule form {:keys [replace-form message filename form-meta exception]}] 24 | (let [form-meta (or form-meta (meta form))] 25 | (->Diagnostic 26 | (:full-name rule) 27 | form 28 | (or message (:message rule)) 29 | replace-form 30 | (:line form-meta) 31 | (:column form-meta) 32 | (:end-line form-meta) 33 | (:end-column form-meta) 34 | (or filename (:filename ctx)) 35 | exception)))) 36 | 37 | (comment 38 | (let [ctx {:filename "adsf"} 39 | rule {:full-name 'style/defn-fn 40 | :message "message"} 41 | form ^{:line 1 :column 2 :end-line 3 :end-column 4} '(1 2 3) 42 | opts {:message "other"}] 43 | (user/quick-bench 44 | (->diagnostic ctx rule form opts)))) 45 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/style/multiple_arity_order.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.style.multiple-arity-order 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]] 9 | [noahtheduke.splint.rules.helpers :refer [defn??]] 10 | [noahtheduke.splint.utils :refer [drop-quote]])) 11 | 12 | (set! *warn-on-reflection* true) 13 | 14 | (defrule style/multiple-arity-order 15 | "Sort the arities of a function from fewest to most arguments. 16 | 17 | @examples 18 | 19 | ; avoid 20 | (defn foo 21 | ([x] (foo x 1)) 22 | ([x y & more] (reduce foo (+ x y) more)) 23 | ([x y] (+ x y))) 24 | 25 | ; prefer 26 | (defn foo 27 | ([x] (foo x 1)) 28 | ([x y] (+ x y)) 29 | ([x y & more] (reduce foo (+ x y) more))) 30 | " 31 | {:pattern '((? defn defn??) ?name ?*args) 32 | :message "defn arities should be sorted fewest to most arguments." 33 | :autocorrect true 34 | :on-match (fn [ctx rule form {:syms [?defn ?name]}] 35 | (when-let [defn-form (:splint/defn-form (meta form))] 36 | (when-let [arglists (drop-quote (:arglists defn-form))] 37 | (when-not (= arglists (sort-by count arglists)) 38 | (let [new-arities (sort-by (comp count first) (:arities defn-form)) 39 | new-form (list* ?defn ?name new-arities)] 40 | (->diagnostic ctx rule form {:replace-form new-form}))))))}) 41 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/no_op_assignment.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.no-op-assignment 6 | (:require 7 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 8 | [noahtheduke.splint.rules :refer [defrule]])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defrule lint/no-op-assignment 13 | "If the bind is a symbol and the expr is the same symbol, just use the expr directly. (Otherwise, indicates a potential bug.) 14 | 15 | Skips if the expr is a reader conditional or has a type-hint. 16 | 17 | @examples 18 | 19 | ; avoid 20 | (let [foo foo] ...) 21 | 22 | ; ignores 23 | (let [foo #?(:clj foo :cljs (js-foo-getter))] ...) 24 | (let [foo ^ArrayList foo] ...) 25 | " 26 | {:pattern '(let [?+bindings] ?*_) 27 | :on-match (fn [ctx rule form {:syms [?bindings]}] 28 | (when (even? (count ?bindings)) 29 | (for [[bind expr] (partition 2 ?bindings) 30 | :when (and (symbol? bind) 31 | (= bind expr)) 32 | :let [expr-meta (meta expr)] 33 | :when (not (or (:splint/reader-cond expr-meta) 34 | (:tag expr-meta) 35 | (:tag (meta bind)))) 36 | :let [new-form (with-meta (list bind expr) 37 | (meta bind))]] 38 | (->diagnostic ctx rule new-form {:message "Avoid no-op assignment."}))))}) 39 | -------------------------------------------------------------------------------- /test/noahtheduke/splint/utils_test.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns noahtheduke.splint.utils-test 6 | (:require 7 | [lazytest.core :refer [defdescribe expect it]] 8 | [noahtheduke.splint.utils :as sut])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defdescribe simple-type-test 13 | (map (fn [[input t]] 14 | (it (str t) 15 | (expect (sut/simple-type input) t))) 16 | '[[nil :nil] 17 | [true :boolean] 18 | [1 :number] 19 | ["a" :string] 20 | [:a :keyword] 21 | [a :symbol] 22 | [(1 2 3) :list] 23 | [[1 2 3] :vector] 24 | [{1 2} :map] 25 | [#{1 2 3} :set] 26 | [#"asdf" java.util.regex.Pattern]])) 27 | 28 | (defdescribe support-clojure-version?-test 29 | (it "checks major, minor, and incremental" 30 | (expect 31 | (sut/support-clojure-version? 32 | {:major 1 :minor 12 :incremental 2} 33 | {:major 1 :minor 12 :incremental 2})) 34 | (expect 35 | (not 36 | (sut/support-clojure-version? 37 | {:major 1 :minor 12 :incremental 2} 38 | {:major 1 :minor 12 :incremental 1})))) 39 | (it "doesn't check minor if major is greater" 40 | (expect 41 | (sut/support-clojure-version? 42 | {:major 1 :minor 12} 43 | {:major 3 :minor 0}))) 44 | (it "doesn't check incremental if minor is greater" 45 | (expect 46 | (sut/support-clojure-version? 47 | {:major 1 :minor 9 :incremental 100} 48 | {:major 1 :minor 12 :incremental 0})))) 49 | -------------------------------------------------------------------------------- /src/noahtheduke/splint/rules/lint/no_target_for_method.clj: -------------------------------------------------------------------------------- 1 | ; This Source Code Form is subject to the terms of the Mozilla Public 2 | ; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ; file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | (ns ^:no-doc noahtheduke.splint.rules.lint.no-target-for-method 6 | (:require 7 | [clojure.string :as str] 8 | [noahtheduke.splint.diagnostic :refer [->diagnostic]] 9 | [noahtheduke.splint.rules :refer [defrule]])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (defn instance-interop? [sexp] 14 | (and (symbol? sexp) 15 | (str/starts-with? (name sexp) "."))) 16 | 17 | (defn in-nested-ctx? [ctx] 18 | (when-let [parent-form (:parent-form ctx)] 19 | (and (seq? parent-form) 20 | (#{'doto 'case '-> '->> 'cond-> 'cond->> 'some-> 'some->>} (first parent-form))))) 21 | 22 | (defrule lint/no-target-for-method 23 | "Instance methods require a target instance. If there's none or it's nil, highly likely there's a bug. 24 | 25 | This rule ignores when a find is nested in a `doto` or similar form: `(doto (new java.util.HashMap) (.put \"a\" 1) (.put \"b\" 2))` will not raise a diagnostic. 26 | 27 | @examples 28 | 29 | ; avoid 30 | (.length) 31 | (.length nil) 32 | (String/.length) 33 | (String/.length nil) 34 | 35 | ; prefer 36 | (.length foo) 37 | (String/.length foo) 38 | " 39 | {:pattern '((? ?fn instance-interop?) (?? _ nil?)) 40 | :on-match (fn [ctx rule form {:syms [?fn]}] 41 | (let [parent (:parent-form ctx)] 42 | (when-not (in-nested-ctx? ctx) 43 | (->diagnostic ctx rule form 44 | {:message "Instance methods require a target instance."}))))}) 45 | --------------------------------------------------------------------------------