clojurescript

pez 2025-08-14T07:13:52.719919Z

Accessing dashed keys in JS objects confuses me still.

❯ clj -Sdeps '{:deps {org.clojure/clojurescript {:mvn/version "1.12.42"}}}' -M -m cljs.main -re node

ClojureScript 1.12.42
cljs.user=> (aget #js {:foo-bar "foo"} "foo-bar")
"foo"
cljs.user=> (unchecked-get #js {:foo-bar "foo"} "foo-bar")
"foo"
cljs.user=> (.-foo-bar #js {:foo-bar "foo"})
nil
cljs.user=> (.-foo #js {:foo "foo"})
"foo"
Where can I read about this behaviour? (I’ve been doing mostly Joyride coding lately, and since the access syntax works fine with dashed keys in SCI, I guess I had lowered my guard a bit, doing some work in “real” ClojureScript.)

borkdude 2025-08-14T07:15:08.830729Z

The key in (.-foo-bar #js {:foo-bar "foo"}) get munged

borkdude 2025-08-14T07:17:06.920459Z

I guess this is a bug in SCI:

(sci/eval-string "(.-foo-bar #js {:foo-bar \"foo\"})" {:classes {:allow :all}})
"foo"

thheller 2025-08-14T07:17:15.377739Z

yeah thats it basically. symbols get munged, i.e - turns into _. strings are untouched and left as is

pez 2025-08-14T07:17:56.793749Z

uncheck-get knows how to unmunge things?

thheller 2025-08-14T07:18:06.276839Z

is case of #js also just uses (name kw), so no munging there either. js is fine with x["foo-bar"]

thheller 2025-08-14T07:18:12.001979Z

no, never munges anything. just accepts a string as-is

borkdude 2025-08-14T07:18:29.207769Z

@pez unchecked-get compiles to arr["foo-bar"]

borkdude 2025-08-14T07:18:58.949949Z

but (.-foo-bar ...) compiles to arr.foo_bar

🙏 1
pez 2025-08-14T07:19:11.195999Z

Ah!

thheller 2025-08-14T07:20:23.914159Z

x.foo-bar looks fine to "our eyes" but is actually x.foo MINUS bar as far as JS is concerned 😉, so has to be munged

🙏 2
borkdude 2025-08-14T07:20:55.948139Z

pez 2025-08-14T07:25:32.174869Z

I wonder how my Joyride scripts will fare if that is fixed… 🤔

borkdude 2025-08-14T07:27:30.177859Z

you can move to (.-foo_bar ...) today to prepare ;)

pez 2025-08-14T07:29:01.130989Z

I forgot to include that in my cljs examples above:

cljs.user=> (.-foo_bar #js {:foo-bar "foo"})
nil

borkdude 2025-08-14T07:30:57.470099Z

yeah. the key in JS is still "foo-bar" in the map. it's unfortunate. perhaps clj-kondo could warn on method / property calls with hyphens since it can be a mismatch?

➕ 2
borkdude 2025-08-14T07:31:14.463799Z

so the user should write (.-foo_bar ...) to avoid the warning?

borkdude 2025-08-14T07:55:05.121369Z

Here is how you can find those usages:

rg "\(\.-?[a-zA-Z0-9_]+-"

borkdude 2025-08-14T08:09:46.701349Z

here's another related case:

cljs.user=> (def x #js {})
#'cljs.user/x
cljs.user=> (set! x -foo-bar 1)
1
cljs.user=> x
#js {:foo_bar 1}

🙏 1
pez 2025-08-14T09:09:58.164789Z

Sounds great to let clj-kondo help with navigating this. It would have been very good for my session late yesterday night, where too-tired me struggled. 😃 And Copilot will also be better guided with this support from clj-kondo.

borkdude 2025-08-14T09:14:17.112259Z

What would be a good way of going about this for clj-kondo? To always warn when you're using a property with (.- ..) etc that is munged differently than plainly written?

borkdude 2025-08-14T09:15:00.728109Z

and what would be the advice clj-kondo gives about this? use unchecked-get (which is documented as INTERNAL) or otherwise?

pez 2025-08-14T09:46:22.063059Z

> To always warn when you’re using a property with (.- ..) etc that is munged differently than plainly written? That sounds good to me.

pez 2025-08-14T09:49:36.833389Z

As for what to advice… Would be nice if there was a checked-get 😃 I use unchecked-get with less hesitation since when I got the impression that @thheller uses it. But I agree it is not super obvious for the linter to advice to use it. Could be worth a separate thread to gather input?

pez 2025-08-14T09:50:14.362639Z

An MVP is a warning, and no advice.

borkdude 2025-08-14T09:50:33.333389Z

I'm not completely sure about this all since it's only an issue because it's an oversight in SCI

borkdude 2025-08-14T09:51:08.082969Z

well, a warning should have something that goes with it like: this is what you can do instead, since people don't want to live with warnings

pez 2025-08-14T09:51:11.109489Z

I don’t fully agree. Also without SCI it is a footgun.

borkdude 2025-08-14T09:51:36.490129Z

is this also a footgun on its own? #js {:foo-bar 1}

pez 2025-08-14T09:52:05.905549Z

I guess it is. Sort of the root footgun?

borkdude 2025-08-14T09:52:46.836969Z

it's completely fine when you use gobject/get etc of course

borkdude 2025-08-14T09:52:55.556739Z

and in JS: obj["foo-bar"]

pez 2025-08-14T09:53:11.664149Z

Maybe that’s the advice? (gobject/get)

borkdude 2025-08-14T09:53:59.175929Z

sometimes it is but e.g. in squint you don't have gobject/get. aget also works but theoretically it's warned against since it's for arrays (but it generates the same code as unchecked-get... so wtf?)

borkdude 2025-08-14T09:54:54.567559Z

I guess the advice for (.-foo-bar ...) would be to explicitlly use (.-foo_bar ...)?

pez 2025-08-14T09:58:38.758819Z

Well..: https://clojurians.slack.com/archives/C03S1L9DN/p1755156541130989?thread_ts=1755155632.719919&cid=C03S1L9DN Maybe the advice could be to look gobject/get, aget, and unchecked-get up?

borkdude 2025-08-14T09:59:57.117859Z

I could just warn with Munged property or method call and just let the user do whatever

borkdude 2025-08-14T10:00:02.120099Z

the MVP as you say

borkdude 2025-08-14T10:00:07.125449Z

and it will be an optional rule

borkdude 2025-08-14T10:01:15.028419Z

I wonder if this is also relevant to JVM Clojure somehow. I guess it could be:

user=> (deftype Foo [foo-bar])
user.Foo
user=> (.-foo-bar (->Foo 1))
1
user=> (deftype Foo [foo_bar])
user.Foo
user=> (.-foo-bar (->Foo 1))
1
user=> (.-foo_bar (->Foo 1))
1

borkdude 2025-08-14T10:04:59.936689Z

hmm:

user=> (defrecord Foo [foo_bar])
user.Foo
user=> (into {} (->Foo 1))
{:foo_bar 1}
user=> (defrecord Foo [foo-bar])
user.Foo
user=> (into {} (->Foo 1))
{:foo-bar 1}

borkdude 2025-08-14T10:05:42.448019Z

eh wow:

user=> (defrecord Foo [foo-bar])
user.Foo
user=> (into {} (->Foo 1))
{:foo-bar 1}
user=> (.-foo-bar (->Foo 1))
1
user=> (.-foo_bar (->Foo 1))
1

borkdude 2025-08-14T10:06:18.070059Z

I guess I could "fix" SCI to look for both properties if the property is munged... which doesn't sound like a good plan but maybe it is?

borkdude 2025-08-14T10:07:33.149829Z

Ah, this also works in CLJS:

cljs.user=> (defrecord Foo [foo-bar])
cljs.user/Foo
cljs.user=> (.-foo-bar (->Foo 1))
1
cljs.user=> (.-foo_bar (->Foo 1))
1

borkdude 2025-08-14T10:15:46.773479Z

wrote this issue: https://github.com/clj-kondo/clj-kondo/issues/2605

borkdude 2025-08-14T10:35:24.930999Z

getting lots of warnings from things like this: https://github.com/clojure/clojurescript/blob/ddf3cfe14e71b342fbe29ff858c620752ccfc4cb/src/main/cljs/cljs/core.cljs#L7523

pez 2025-08-14T10:41:33.990159Z

https://clojurians.slack.com/archives/C03S1L9DN/p1755165942448019?thread_ts=1755155632.719919&cid=C03S1L9DN Wow, indeed. This could be something for the second edition of Clojure Brain Teasers, @alexmiller?

borkdude 2025-08-14T14:22:03.468229Z

I'm trying to fix this in SCI, but as you said, there's the risk of breaking existing scripts. What if we didn't fix this at all?

borkdude 2025-08-14T14:22:18.880179Z

I need @lee to write a decision matrix :P

😂 1
lread 2025-08-14T14:29:23.605729Z

Option 1: do nothing

lread 2025-08-14T14:31:44.536569Z

I'm very new to cljs and don't have deep knowledge of JS... so might not have enough expertise here to chime in.

borkdude 2025-08-14T14:32:32.985279Z

Option 2: do both

borkdude 2025-08-14T14:32:39.236279Z

Option 3: fix and break

borkdude 2025-08-14T14:33:15.849119Z

Option 2 might also be breaking in some obscure cases like {:foo-bar 1 :foo_bar 1}

pez 2025-08-14T14:34:27.639629Z

I think that not fixing at all in SCI may be enough if clj-kondo is there telling you that your access syntax may be problematic.

borkdude 2025-08-14T14:35:06.761309Z

I think the clj-kondo rule will be completely optional though

borkdude 2025-08-14T14:35:17.106249Z

since it's annoying in the many cases that I discovered

pez 2025-08-14T14:35:42.028449Z

I’d like it to be opt-out, but clj-kondo is yours. 😃

borkdude 2025-08-14T14:43:50.279609Z

I'm tempted to just fix it since CLJS is the reference implementation

borkdude 2025-08-14T14:44:06.290909Z

I pushed commit d4a17424ee7f160eaba1a44317e43ff5015995b7 that you can try to see if anything breaks

borkdude 2025-08-14T14:44:18.914759Z

I'll bump nbb tests to see if anything breaks

borkdude 2025-08-14T14:56:32.436799Z

nbb tests all passing. this might be a fairly niche thing

pez 2025-08-14T15:04:37.685549Z

It may be a PEZ thing, because I love the dot access syntax. 😃

pez 2025-08-14T15:59:32.452699Z

I don’t find any uses of munged access in any of the public Joyride examples, nor my own Joyride scripts, btw. I’ll try to get a test of the commit in later today.

borkdude 2025-08-14T17:46:17.248469Z

did @lilactown comment here before or not? I think I saw a notification on my phone but not sure

borkdude 2025-08-21T11:11:51.908729Z

@pez I decided to just bite the bullet and fix it according to CLJS. I don't see a nice way out of this otherwise

👍 1
borkdude 2025-08-21T11:12:24.396699Z

Pushed a new SCI release with the "fix"

raspasov 2025-08-26T09:28:17.419689Z

> realized I was giving my personal opinion as gospel Those are often the best! 😄 For an average of all opinions, there’s an LLM.

lilactown 2025-08-18T16:39:33.511549Z

I did but I deleted it 🙂 realized I was giving my personal opinion as gospel

😇 1
bhauman 2025-08-14T13:49:38.036249Z

ClojureMCP v0.1.8-alpha https://github.com/bhauman/clojure-mcp/blob/main/CHANGELOG.md#018-alpha---2025-08-13

❤️ 2