summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAsko Nõmm <asko@nmm.ee>2025-09-24 16:47:18 +0300
committerAsko Nõmm <asko@nmm.ee>2025-09-24 16:47:18 +0300
commitb94e6243d0f557a2221e3369605933de5c98de74 (patch)
treec5af975e7a3bb45cc3dd1aac381c479089cc73ca
parentafbb8d284349c46f8c5cd999e21f95c62572fda9 (diff)
Fix issue where one could not use ints or longs in the $ macro, and add clj-kondo hook for better validation.
-rw-r--r--resources/clj-kondo/config.edn2
-rw-r--r--resources/clj-kondo/hooks/dompa.clj37
-rw-r--r--src/dompa/utils.cljc29
3 files changed, 50 insertions, 18 deletions
diff --git a/resources/clj-kondo/config.edn b/resources/clj-kondo/config.edn
new file mode 100644
index 0000000..fa8d235
--- /dev/null
+++ b/resources/clj-kondo/config.edn
@@ -0,0 +1,2 @@
+{:hooks {:analyze-call {dompa.utils/$ hooks.dompa/$}}
+ :linters {:dompa.utils/$-arg-validation {:level :warning}}} \ No newline at end of file
diff --git a/resources/clj-kondo/hooks/dompa.clj b/resources/clj-kondo/hooks/dompa.clj
new file mode 100644
index 0000000..b0eee7a
--- /dev/null
+++ b/resources/clj-kondo/hooks/dompa.clj
@@ -0,0 +1,37 @@
+(ns hooks.dompa
+ (:require [clj-kondo.hooks-api :as api]))
+
+(defn string-like? [x]
+ (or (api/string-node? x)
+ (api/token-node? x)))
+
+(defn $ [{:keys [node]}]
+ (let [[_ first-arg & rest-args] (:children node)]
+ (cond
+ ; if the first arg is string-like, then no sequences
+ ; are allowed, because you are only allowed to concat
+ ; strings, i.e. whatever (str) can do.
+ (or (api/string-node? first-arg)
+ (api/token-node? first-arg))
+ (let [invalid-args (filter #(not (string-like? %)) rest-args)]
+ (doall
+ (for [invalid-arg invalid-args]
+ (api/reg-finding!
+ (assoc (meta invalid-arg)
+ :message (str "Invalid argument type. When creating a text node, "
+ "only literal values (strings, numbers and symbols) are allowed.")
+ :type :dompa.utils/$-arg-validation)))))
+
+ ; if the first arg is a keyword, then the second argument can only be
+ ; a sequence or a map.
+ (and (api/keyword-node? first-arg)
+ (seq rest-args)
+ (not (or (api/map-node? (first rest-args))
+ (api/list-node? (first rest-args)))))
+ (api/reg-finding!
+ (assoc (meta (first rest-args))
+ :message (str "Invalid argument type. When creating a non-text node, "
+ "the second argument must be a sequence or a map. "
+ "In other words, the second argument must be an attribute map "
+ "or sequence of other nodes created with the $ macro.")
+ :type :dompa.utils/$-arg-validation))))) \ No newline at end of file
diff --git a/src/dompa/utils.cljc b/src/dompa/utils.cljc
index 0e17875..d76ba28 100644
--- a/src/dompa/utils.cljc
+++ b/src/dompa/utils.cljc
@@ -1,6 +1,5 @@
(ns dompa.utils
- (:require [dompa.nodes :as nodes]
- [criterium.core :as c]))
+ (:require [dompa.nodes :as nodes]))
(defmacro defhtml
{:clj-kondo/lint-as 'clojure.core/defn}
@@ -9,7 +8,7 @@
`(defn ~name ~args
(nodes/->html (vector ~@elements)))))
-(defn flattench-xf []
+(defn ->flat-xf []
(fn [rf]
(letfn [(step [result input]
(if (sequential? input)
@@ -20,15 +19,14 @@
([result] (rf result)) ;; completion
([result input] (step result input)))))) ;; step
-(defn flattench [children]
- (into [] (flattench-xf) children))
+(defn ->flat [children]
+ (into [] (->flat-xf) children))
(defmacro $
- {:clj-kondo/lint-as 'clojure.core/list}
[name & opts]
`(if (string? ~name)
{:node/name :dompa/text
- :node/value (apply str ~name ~@opts)}
+ :node/value (str ~name ~@opts)}
(let [opts# (list ~@opts)
first-opt# (first opts#)
attrs?# (and (map? first-opt#)
@@ -37,18 +35,13 @@
children# (if attrs?# (rest opts#) opts#)]
(cond-> {:node/name ~name}
attrs?# (assoc :node/attrs attrs#)
- (seq children#) (assoc :node/children (flattench children#))))))
-
-(defn bench-n []
- (c/quick-bench
- (dotimes [_ 1500]
- ($ :div {:class "container"}
- (map #($ %) ["a" "b" "c"])
- ($ "hello world")))))
+ (seq children#) (assoc :node/children (->flat children#))))))
(defhtml test-page []
- ($ :div
- (map #($ %) ["a" "b" "c"])
- ($ "hello world")))
+ (let [n 123]
+ ($ :div
+ ($ "hello world" n))
+ ($ "hello")
+ ($ :div "test")))
(test-page)