clojurescript 2025-08-14

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.)

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

I guess this is a bug in SCI:

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

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

uncheck-get knows how to unmunge things?

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

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

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

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

🙏 1

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

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

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

I forgot to include that in my cljs examples above:

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

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

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

Here is how you can find those usages:

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

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

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.

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?

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

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

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?

An MVP is a warning, and no advice.

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

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

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

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

I guess it is. Sort of the root footgun?

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

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

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

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?)

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

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

the MVP as you say

and it will be an optional rule

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

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}

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

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?

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

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?

I need @lee to write a decision matrix :P

😂 1

Option 1: do nothing

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

Option 2: do both

Option 3: fix and break

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

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.

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

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

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

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

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

I'll bump nbb tests to see if anything breaks

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

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

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.

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

@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

Pushed a new SCI release with the "fix"

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

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

😇 1