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
Ah!
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
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"})
nilyeah. 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?
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}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 ...)?
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?
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))
1hmm:
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))
1I 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))
1wrote this issue: https://github.com/clj-kondo/clj-kondo/issues/2605
getting lots of warnings from things like this: https://github.com/clojure/clojurescript/blob/ddf3cfe14e71b342fbe29ff858c620752ccfc4cb/src/main/cljs/cljs/core.cljs#L7523
https://gist.github.com/borkdude/c04322e26b4d88ae18b623f4527feab4
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?
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?
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
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
ClojureMCP v0.1.8-alpha https://github.com/bhauman/clojure-mcp/blob/main/CHANGELOG.md#018-alpha---2025-08-13