Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions .clj-kondo/hooks/ornament.clj
Original file line number Diff line number Diff line change
@@ -1,35 +1,46 @@
(ns hooks.ornament
(:require [clj-kondo.hooks-api :as api]))

(defonce styled-registry (atom #{}))

(defn- symbol-node? [node]
(and (api/token-node? node)
(symbol? (api/sexpr node))))

(defn- styled-component? [node]
(and (symbol-node? node)
(contains? @styled-registry (api/sexpr node))))

(defn defstyled [{:keys [node]}]
(let [[class-name html-tag & more] (rest (:children node))
_ (when-not (and (api/token-node? class-name)
_ (when-not (and (symbol-node? class-name)
(simple-symbol? (api/sexpr class-name)))
(api/reg-finding! {:row (:row (meta class-name))
:col (:col (meta class-name))
:message "Style name must be a symbol"
:message "Style name must be a simple symbol"
:type :lambdaisland.ornament/invalid-syntax}))
; _ (prn :class-name class-name)
_ (when-not (api/keyword-node? html-tag)
_ (when-not (or (api/keyword-node? html-tag)
(styled-component? html-tag))
(api/reg-finding! {:row (:row (meta html-tag))
:col (:col (meta html-tag))
:message "Tag must be a keyword or an ornament-styled-component"
:type :lambdaisland.ornament/invalid-syntax}))
; _ (prn :html-tag html-tag)
; _ (prn :more more)
fn-tag (first (drop-while (fn [x]
(or (api/string-node? x)
(api/keyword-node? x)
(api/map-node? x)
(api/vector-node? x)))
(api/vector-node? x)
(api/token-node? x)))
more))
; _ (prn :fn-tag fn-tag)
_ (when (and fn-tag
(not (api/list-node? fn-tag)))
(api/reg-finding! {:row (:row (meta fn-tag))
:col (:col (meta fn-tag))
:message "Function part (if present) must be a list"
:type :lambdaisland.ornament/invalid-syntax}))]
:type :lambdaisland.ornament/invalid-syntax}))
symbol-tag? (symbol-node? html-tag)]
;; Register this component in the styled-registry
(swap! styled-registry conj (api/sexpr class-name))
(if (api/list-node? fn-tag)
(let [[binding-vec & body] (:children fn-tag)
fn-node (api/list-node
Expand All @@ -38,15 +49,23 @@
binding-vec
body))
new-def-node (api/list-node
(list (api/token-node 'def)
class-name
fn-node))]
(prn :new-def-node (api/sexpr new-def-node))
{:node new-def-node})
;; nil node
(let [def-class-form (api/list-node
(if symbol-tag?
(list (api/token-node 'def)
class-name
(api/list-node
(list (api/token-node 'do)
html-tag
fn-node)))
(list (api/token-node 'def)
class-name
(api/token-node 'nil)))]
(prn :def-class-form (api/sexpr def-class-form))
fn-node)))]
{:node new-def-node})
(let [def-class-form (api/list-node
(if symbol-tag?
(list (api/token-node 'def)
class-name
html-tag)
(list (api/token-node 'def)
class-name
(api/token-node 'nil))))]
{:node def-class-form}))))
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
uses: DeLaGuardo/setup-clojure@master
with:
cli: '1.10.3.943'
clj-kondo: 'latest'

- name: 🗝 maven cache
uses: actions/cache@v4
Expand Down
99 changes: 99 additions & 0 deletions test/lambdaisland/ornament/clj_kondo_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
(ns lambdaisland.ornament.clj-kondo-test
(:require [clojure.test :refer [deftest testing is]]
[clojure.java.shell :as shell]
[clojure.edn :as edn]))

(defn- lint-string
"Run clj-kondo --lint on a code string, return findings as EDN."
[code]
(let [{:keys [out]} (shell/sh "clj-kondo"
"--lint" "-"
"--config-dir" ".clj-kondo"
"--config" "{:output {:format :edn}}"
:in code)]
(:findings (edn/read-string out))))

(defn- ornament-warnings
"Return only the ornament/invalid-syntax findings."
[findings]
(filter #(= :lambdaisland.ornament/invalid-syntax (:type %)) findings))

(defn- warning-messages
"Return the set of warning message strings from findings."
[findings]
(into #{} (map :message) findings))

;; Valid patterns — should produce no ornament warnings

(deftest keyword-tag-test
(testing "keyword tag produces no warnings"
(let [warnings (-> "(require '[lambdaisland.ornament :as o])
(o/defstyled foo :div)"
lint-string
ornament-warnings)]
(is (empty? warnings)))))

(deftest styled-component-as-tag-test
(testing "styled component used as tag produces no warnings"
(let [warnings (-> "(require '[lambdaisland.ornament :as o])
(o/defstyled base :span {:color \"blue\"})
(o/defstyled inherited base)"
lint-string
ornament-warnings)]
(is (empty? warnings)))))

(deftest symbol-in-rules-test
(testing "symbol references in rules position produce no warnings"
(let [warnings (-> "(require '[lambdaisland.ornament :as o])
(o/defstyled bold :span :font-bold)
(o/defstyled heading :h1 bold :text-3xl)"
lint-string
ornament-warnings)]
(is (empty? warnings)))))

(deftest fn-body-test
(testing "defstyled with fn body produces no warnings"
(let [warnings (-> "(require '[lambdaisland.ornament :as o])
(o/defstyled foo :div ([props] [:div props]))"
lint-string
ornament-warnings)]
(is (empty? warnings)))))

;; Invalid patterns — should produce ornament warnings

(deftest non-symbol-name-test
(testing "non-symbol name produces warning"
(let [msgs (-> "(require '[lambdaisland.ornament :as o])
(o/defstyled 123 :div)"
lint-string
ornament-warnings
warning-messages)]
(is (contains? msgs "Style name must be a simple symbol")))))

(deftest non-keyword-non-styled-tag-test
(testing "numeric literal as tag produces warning"
(let [msgs (-> "(require '[lambdaisland.ornament :as o])
(o/defstyled foo 123)"
lint-string
ornament-warnings
warning-messages)]
(is (contains? msgs "Tag must be a keyword or an ornament-styled-component")))))

(deftest regular-def-as-tag-test
(testing "regular def used as tag produces warning"
(let [msgs (-> "(require '[lambdaisland.ornament :as o])
(def not-styled 42)
(o/defstyled foo not-styled)"
lint-string
ornament-warnings
warning-messages)]
(is (contains? msgs "Tag must be a keyword or an ornament-styled-component")))))

(deftest invalid-fn-part-test
(testing "non-list in fn position produces warning"
(let [msgs (-> "(require '[lambdaisland.ornament :as o])
(o/defstyled foo :div #{:a :b})"
lint-string
ornament-warnings
warning-messages)]
(is (contains? msgs "Function part (if present) must be a list")))))
Loading