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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Added

- Export clj-kondo hooks and config via `resources/clj-kondo.exports/` so library
consumers can activate them by running
`clj-kondo --copy-configs --dependencies --lint classpath`

## Fixed

## Changed
Expand Down
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -945,11 +945,26 @@ meta-merge). e.g. `{:colors ^:replace {...}}`)

## clj-kondo Support

This library includes a built-in clj-kondo configuration to improve linting for Ornament components.
Ornament ships clj-kondo hooks and configuration that validate `defstyled` forms — catching bad tag names, invalid syntax, and providing correct `def`-like analysis.

To use the configuration, simply copy the .clj-kondo/ directory from this repository into the root of your project.
The hooks are bundled in the JAR under `clj-kondo.exports/`. To activate them in your project:

After copying the directory, clj-kondo will automatically use this configuration to provide more accurate linting and reduce false positives in your Ornament projects.
1. Ensure a `.clj-kondo` directory exists at your project root:
```
mkdir -p .clj-kondo
```

2. Copy the configs from your dependencies:
```
clj-kondo --lint "$(clojure -Spath)" --copy-configs --skip-lint
```

3. Warm the linting cache:
```
clj-kondo --lint "$(clojure -Spath)" --dependencies --parallel
```

After that, clj-kondo will automatically validate `defstyled` forms in your project. Consider checking the copied configs into version control.

## Babashka compatibility

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{:lint-as {lambdaisland.ornament/defprop clojure.core/def
lambdaisland.ornament/defrules clojure.core/def}
:hooks {:analyze-call {lambdaisland.ornament/defstyled hooks.ornament/defstyled}}
:linters {:lambdaisland.ornament/invalid-syntax
{:level :warning}}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
(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 (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 simple symbol"
:type :lambdaisland.ornament/invalid-syntax}))
_ (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}))
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/token-node? x)))
more))
_ (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}))
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
(list*
(api/token-node 'fn)
binding-vec
body))
new-def-node (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
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}))))
Loading