rooms-kit / .clj-kondo / hooks / defclass.clj
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
(ns hooks.defclass
  "clj-kondo hook for shadow.cljs.modern/defclass.

   defclass is (defclass Name (extends Base)? (field f v)*
   (constructor [this args] body*) Protocol (method [this args] body*)* …).
   clj-kondo has no idea Name is being defined, so every reference to it —
   including the internals-style re-exports from other namespaces — reports
   Unresolved symbol, and so does every constructor/method parameter.

   Rewrite it into a form clj-kondo does understand: a def for the name, and
   an fn per constructor/method so the parameters bind their bodies. Bare
   protocol symbols (Object, IDeref) are dropped — they are not call sites.

   Three details are load-bearing, each measured against this tree:
   - the def needs a VALUE. A bare (def x) is an Uninitialized var warning,
     31 of them across chat, board and rooms-kit.
   - (extends Base) must survive as a reference to Base, or the require that
     brought Base in looks unused — node:events/EventEmitter in rooms-kit's
     chat.cljs and board.cljs, ws/WebSocket in node/env.cljs.
   - `super` may be bound ONLY for a class that extends. Bind it always and
     every non-extending class reports an unused binding; bind it never and
     every extending constructor reports Unresolved symbol: super."
  (:require [clj-kondo.hooks-api :as api]))

(defn- form-named?
  [n nm]
  (and (= :list (api/tag n))
       (= nm (str (first (:children n))))))

(defn- method-like?
  "A (sym [params] body*) list — the shape both constructor and methods take."
  [n]
  (let [[head params] (:children n)]
    (and (= :list (api/tag n))
         (some? head) (api/token-node? head)
         (some? params) (= :vector (api/tag params)))))

(defn defclass [{:keys [node]}]
  (let [[_ name-node & body] (:children node)
        extends-node (first (filter #(form-named? % "extends") body))
        ;; keep the extended class as a real reference so its require counts
        extends (when extends-node
                  (api/list-node (list* (api/token-node 'do)
                                        (rest (:children extends-node)))))
        params-of (fn [params]
                    (if extends-node
                      (api/vector-node (cons (api/token-node 'super)
                                             (:children params)))
                      params))
        fns (for [n body :when (and (not (form-named? n "extends"))
                                    (not (form-named? n "field"))
                                    (method-like? n))]
              (let [[_ params & fbody] (:children n)]
                (api/list-node (list* (api/token-node 'fn) (params-of params) fbody))))
        ;; (field f v): v is real code and may call things
        fields (for [n body :when (form-named? n "field")]
                 (api/list-node (list* (api/token-node 'do) (drop 2 (:children n)))))]
    {:node (api/list-node
            (list* (api/token-node 'do)
                   (api/list-node [(api/token-node 'def) name-node (api/token-node 'nil)])
                   (concat (when extends [extends]) fields fns)))}))

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/rooms-kit.git