From b8b3067e53cc829eeabf570c3269f0039b6ef89b Mon Sep 17 00:00:00 2001 From: sabahmax-dev <330070908+sabahmax-dev@users.noreply.github.com> Date: Thu, 17 Sep 2026 11:53:18 +0300 Subject: [PATCH] Add js-obj destructuring macro with rest collection Add a macro in `src/mate/js_obj.cljc` to destructure JavaScript objects and collect the remaining properties as rest. --- src/mate/js_obj.cljc | 98 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/mate/js_obj.cljc diff --git a/src/mate/js_obj.cljc b/src/mate/js_obj.cljc new file mode 100644 index 0000000..1b491db --- /dev/null +++ b/src/mate/js_obj.cljc @@ -0,0 +1,98 @@ +#?(:cljs + (ns mate.js-obj + {:doc "Macros for destructuring JavaScript objects."})) + +#?(:cljs + (defn- binding-sym + [k] + (cond + (symbol? k) k + (keyword? k) (symbol (name k)) + (string? k) (symbol k) + :else (throw (ex-info "Invalid destructuring key" {:key k}))))) + +#?(:cljs + (defn- lookup-key + [k] + (cond + (keyword? k) (name k) + (string? k) k + (symbol? k) (name k) + :else (str k)))) + +#?(:cljs + (defn- normalized-or + [or] + (into {} + (map (fn [[k v]] + [(lookup-key k) v]) + or))) + +#?(:cljs + (defmacro destructure-js-obj + "Destructure a JavaScript object expression. + + Accepts the same map-style destructuring as `let`, plus `:rest`: + + (destructure-js-obj + [{:keys [a b] + :as obj + :or {a 0} + :rest rest}] + js-obj-expr + body...) + + `:rest` binds a map containing every entry of the object that was not + explicitly destructured." + [bindings expr & body] + (if (symbol? bindings) + `(let [~bindings ~expr] + ~@body) + (do + (when-not (map? bindings) + (throw (ex-info "destructure-js-obj expects a binding map or symbol" {}))) + (let [or (normalized-or (or (get bindings :or) {})) + keys (get bindings :keys []) + strs (get bindings :strs []) + as (get bindings :as) + rest-sym (get bindings :rest) + bound-keys (into #{} + (concat (map lookup-key keys) + (map lookup-key strs))) + obj (gen-sym 'obj) + key-bindings + (vec + (concat + (for [k keys] + (let [sym (binding-sym k) + ks (lookup-key k) + default (get or ks)] + (if (not (nil? default)) + [sym `(let [v# (get ~obj ~ks)] + (if (nil? v#) + ~default + v#))] + [sym `(let [v# (get ~obj ~ks)] + v#)]))) + (for [k strs] + (let [sym (binding-sym k) + ks (lookup-key k) + default (get or ks)] + (if (not (nil? default)) + [sym `(let [v# (get ~obj ~ks)] + (if (nil? v#) + ~default + v#))] + [sym `(let [v# (get ~obj ~ks)] + v#)]))))) + rest-binding + (when rest-sym + [rest-sym `(into {} + (for [k (js-keys ~obj) + :when (not (contains? ~bound-keys k))] + [k (get ~obj k)])))] + as-binding (when as [as obj]) + let-bindings (vec (concat key-bindings as-binding rest-binding))] + `(let [~obj ~expr + ~@let-bindings] + ~@body))))