Skip to content

Commit 700e125

Browse files
committed
Add clj-kondo hook for require-python macro
- Add require_python.clj hook that transforms require-python calls into standard require forms that clj-kondo can analyze - Strip libpython-specific flags: :bind-ns, :reload, :no-arglists - Generate def forms for :bind-ns vars and :refer symbols - Handle :refer with vectors, :all, or :* - Support both v1 (libpython-clj.require) and v2 (libpython-clj2.require) - Replace :lint-as approach with proper analyze-call hooks This eliminates false positive "Unknown require option" warnings for libpython-clj's Python interop macros.
1 parent 02dbcf4 commit 700e125

File tree

2 files changed

+112
-4
lines changed

2 files changed

+112
-4
lines changed

resources/clj-kondo.exports/clj-python/libpython-clj/config.edn

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
{:hooks
22
{:analyze-call {libpython-clj.jna.base/def-pylib-fn
33
hooks.libpython-clj.jna.base.def-pylib-fn/def-pylib-fn
4+
45
libpython-clj.require/import-python
56
hooks.libpython-clj.require.import-python/import-python
67
libpython-clj2.require/import-python
7-
hooks.libpython-clj.require.import-python/import-python}}
8+
hooks.libpython-clj.require.import-python/import-python
89

9-
:lint-as
10-
{libpython-clj.require/require-python clojure.core/require
11-
libpython-clj2.require/require-python clojure.core/require}
10+
libpython-clj.require/require-python
11+
hooks.libpython-clj.require.require-python/require-python
12+
libpython-clj2.require/require-python
13+
hooks.libpython-clj.require.require-python/require-python}}
1214

1315
:linters
1416
{:unresolved-namespace
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
(ns hooks.libpython-clj.require.require-python
2+
(:require [clj-kondo.hooks-api :as api]))
3+
4+
(def ^:private libpython-unary-flags
5+
#{:bind-ns :reload :no-arglists})
6+
7+
(def ^:private libpython-binary-flags
8+
#{:refer})
9+
10+
(defn- get-sexpr [node]
11+
(when node
12+
(try
13+
(api/sexpr node)
14+
(catch Exception _ nil))))
15+
16+
(defn- quoted-form?
17+
[sexpr]
18+
(and (seq? sexpr)
19+
(= 'quote (first sexpr))))
20+
21+
(defn- unquote-sexpr
22+
[sexpr]
23+
(if (quoted-form? sexpr)
24+
(second sexpr)
25+
sexpr))
26+
27+
(defn- extract-bind-ns-var
28+
[spec-data]
29+
(let [pairs (partition 2 (rest spec-data))
30+
bind-ns-val (some (fn [[k v]] (when (= :bind-ns k) v)) pairs)
31+
as-val (some (fn [[k v]] (when (= :as k) v)) pairs)]
32+
(when bind-ns-val
33+
(or as-val
34+
(when-let [first-sym (first spec-data)]
35+
(symbol (last (clojure.string/split (str first-sym) #"\."))))))))
36+
37+
(defn- extract-refer-symbols
38+
[spec-data]
39+
(let [pairs (partition 2 (rest spec-data))
40+
refer-val (some (fn [[k v]] (when (= :refer k) v)) pairs)]
41+
(when (and refer-val (vector? refer-val))
42+
refer-val)))
43+
44+
(defn- filter-spec-data
45+
[spec-data]
46+
(loop [result []
47+
remaining (rest spec-data)]
48+
(if (empty? remaining)
49+
(vec (cons (first spec-data) result))
50+
(let [item (first remaining)
51+
next-item (second remaining)]
52+
(cond
53+
(libpython-unary-flags item)
54+
(if (boolean? next-item)
55+
(recur result (drop 2 remaining))
56+
(recur result (rest remaining)))
57+
58+
(libpython-binary-flags item)
59+
(recur result (drop 2 remaining))
60+
61+
:else
62+
(recur (conj result item) (rest remaining)))))))
63+
64+
(defn- process-spec-data
65+
[sexpr]
66+
(let [unquoted (unquote-sexpr sexpr)]
67+
(cond
68+
(vector? unquoted)
69+
{:spec-data (filter-spec-data unquoted)
70+
:bind-ns-var (extract-bind-ns-var unquoted)
71+
:refer-symbols (extract-refer-symbols unquoted)}
72+
73+
(symbol? unquoted)
74+
{:spec-data unquoted
75+
:bind-ns-var nil
76+
:refer-symbols nil}
77+
78+
:else
79+
{:spec-data unquoted
80+
:bind-ns-var nil
81+
:refer-symbols nil})))
82+
83+
(defn- make-require-form
84+
[specs]
85+
(list* 'require
86+
(map (fn [spec] (list 'quote spec)) specs)))
87+
88+
(defn- make-def-form
89+
[var-name]
90+
(list 'def var-name nil))
91+
92+
(defn require-python
93+
[{:keys [node]}]
94+
(let [form (get-sexpr node)
95+
args (rest form)
96+
processed (map process-spec-data args)
97+
spec-data-list (map :spec-data processed)
98+
bind-ns-vars (filter some? (map :bind-ns-var processed))
99+
refer-symbols (mapcat :refer-symbols processed)
100+
require-form (make-require-form spec-data-list)
101+
def-forms (map make-def-form (concat bind-ns-vars refer-symbols))
102+
result-form (if (seq def-forms)
103+
(list* 'do require-form def-forms)
104+
require-form)
105+
result-node (api/coerce result-form)]
106+
{:node (with-meta result-node (meta node))}))

0 commit comments

Comments
 (0)