diff --git a/CHANGELOG.md b/CHANGELOG.md index a8880b5fc1..17e0872d2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ For a list of breaking changes, check [here](#breaking-changes). - [#2410](https://github.com/clj-kondo/clj-kondo/issues/2410): add `--report-level` flag - [#2416](https://github.com/clj-kondo/clj-kondo/issues/2416): detect empty `require` and `:require` forms ([@NoahTheDuke](https://github.com/NoahTheDuke)) - [#1786](https://github.com/clj-kondo/clj-kondo/issues/1786): Support `gen-interface` (by suppressing unresolved symbols) +- [#2420](https://github.com/clj-kondo/clj-kondo/issues/2420): Detect uneven number of clauses in cond-> and cond->> ## 2024.09.27 diff --git a/src/clj_kondo/impl/macroexpand.clj b/src/clj_kondo/impl/macroexpand.clj index ff4c8c9a75..d1dc5f1fe6 100644 --- a/src/clj_kondo/impl/macroexpand.clj +++ b/src/clj_kondo/impl/macroexpand.clj @@ -1,7 +1,9 @@ (ns clj-kondo.impl.macroexpand {:no-doc true} (:require - [clj-kondo.impl.utils :refer [parse-string tag vector-node list-node token-node]] + [clj-kondo.impl.findings :as findings] + [clj-kondo.impl.utils :refer [parse-string tag vector-node list-node + token-node node->line]] [clojure.walk :as walk])) (set! *warn-on-reflection* true) @@ -54,7 +56,7 @@ (defn expand-cond-> "Expands cond-> and cond->>" - [_ctx expr resolved-as-name] + [ctx expr resolved-as-name] (let [[_ start-expr & clauses] (:children expr) thread-sym (case resolved-as-name cond-> 'clojure.core/-> @@ -74,7 +76,15 @@ (vector-node (list* g start-expr (interleave (repeat g) (butlast steps)))) - (if (empty? steps) g (last steps))])] + (if (empty? steps) g (last steps))]) + clauses-count (count clauses)] + (when (odd? clauses-count) + (findings/reg-finding! ctx + (node->line + (:filename ctx) + expr + :invalid-arity + (str resolved-as-name " requires even number of clauses")))) ret)) (defn expand-doto [_ctx expr] diff --git a/test/clj_kondo/bindings_test.clj b/test/clj_kondo/bindings_test.clj index fee9b05d03..c3565b0761 100644 --- a/test/clj_kondo/bindings_test.clj +++ b/test/clj_kondo/bindings_test.clj @@ -221,7 +221,7 @@ (doseq [test (map :test ts)] test) then)" '{:linters {:unused-binding {:level :warning}}}))) - (is (empty? (lint! "(let [a 1] (cond-> (.getFoo a) x))" + (is (empty? (lint! "(let [a 1] (cond-> (.getFoo a) (some? y) x))" '{:linters {:unused-binding {:level :warning}}}))) (is (empty? (lint! "(defmacro foo [] (let [sym 'my-symbol] `(do '~sym)))" '{:linters {:unused-binding {:level :warning}}}))) diff --git a/test/clj_kondo/invalid_arity_test.clj b/test/clj_kondo/invalid_arity_test.clj index b677bdb1a4..fd26057ce1 100644 --- a/test/clj_kondo/invalid_arity_test.clj +++ b/test/clj_kondo/invalid_arity_test.clj @@ -241,3 +241,17 @@ :level :error, :message "clojure.core/throw is called with 2 args but expects 1"}] (lint! "(throw 1 2)"))) + +(deftest cond-thread-test + (assert-submaps2 + [{:row 1, + :col 1, + :level :error, + :message "cond-> requires even number of clauses"}] + (lint! "(cond-> 42 inc)")) + (assert-submaps2 + [{:row 1, + :col 1, + :level :error, + :message "cond->> requires even number of clauses"}] + (lint! "(cond->> 42 (even? 7) inc (odd? 8))"))) diff --git a/test/clj_kondo/redundant_call_test.clj b/test/clj_kondo/redundant_call_test.clj index d1fdafb5a6..951bab6607 100644 --- a/test/clj_kondo/redundant_call_test.clj +++ b/test/clj_kondo/redundant_call_test.clj @@ -7,8 +7,9 @@ ^:replace {:linters {:redundant-call {:level :warning}}}) (deftest redundant-call-test + (doseq [sym `[-> ->> some-> some->> partial comp merge]] + (is (empty? (lint! (format "(%s 1 identity)" sym) config)))) (doseq [sym `[-> ->> cond-> cond->> some-> some->> partial comp merge]] - (is (empty? (lint! (format "(%s 1 identity)" sym) config))) (assert-submaps [{:level :warning :message (format "Single arg use of %s always returns the arg itself" sym)}] (lint! (format "(%s 1)" sym) config))