This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-01-23
Channels
- # announcements (2)
- # babashka (56)
- # beginners (30)
- # calva (79)
- # cider (5)
- # circleci (3)
- # clj-kondo (84)
- # cljfx (2)
- # cljsrn (3)
- # clojure (264)
- # clojure-czech (3)
- # clojure-europe (18)
- # clojure-spec (3)
- # clojurescript (12)
- # conjure (28)
- # cursive (5)
- # datomic (4)
- # deps-new (22)
- # fulcro (6)
- # graalvm (3)
- # jobs (1)
- # malli (1)
- # off-topic (23)
- # pathom (4)
- # portal (3)
- # practicalli (2)
- # quil (6)
- # reagent (20)
- # remote-jobs (3)
- # reveal (14)
- # ring (10)
- # shadow-cljs (5)
- # spacemacs (4)
- # xtdb (4)
@borkdude can we chat about https://github.com/clj-kondo/clj-kondo/issues/1129 ? I think my view comes down to (s/def ::a)
and (def a)
are very similar, and in my projects, I would treat both a
and ::a
as definitions even though clojure or spec, under the hood, is just storing some state for the var and the keyword. In lsp. I appreciate that kondo analysis differentiates between var-usages
and var-definitions
, and, I think, there's an argument to be made for keywords behaving similarly.
So if you would do something like this, you would have to include all kinds of library specific code
e.g. def
is a special form in clojure, but any macro can make up any kind of def
thing using strings, symbols, keywords, etc. This is user-land behavior
What I think would be generally possible is a list of all keywords, with filename, location, source form (what we call :str so far) and maybe, for some libs we could add a key that this is the defining location or something
but what other libs are using this construct? we can't possibly support all of clojars?
yeah, I agree, it's user-space. But clojure.spec is pretty close to core. And the concept of using a keyword to mean something is generally applicable. One of the things that I ask myself about these analysis prs, is how would one implement linting from this feature?
When you look at the namespace graph, one could maybe derive the first mention of the keyword
but since keywords (e.g. defmethods) are usually used to decouple things, this isn't reliable
writing a sci macro for other libraries that maybe does`(s/def ~name)` would be enough to support re-frame/reg stuff
for lsp it would be sufficient for rename, but there'a goto def/find references (that generally doesn't include the def) that the distinction would be useful for. for linting, I imagine the distinction would allow you to say a spec is unused.
If I would have this use case of unused-ness of a spec, I think I would personally just go through the list of all occurrences of this keyword. Same for re-frame events. Keywords that only occur once are suspicious (for specs, but in general, these are usually typos).
(ns foo)
(s/def ::bar int?)
:foo/bar
I think it's a bit weird to say ":foo/bar" is defined somewhere. I need to think about this a bit more.yeah, I agree it's definitely a different way to think about it. When I added that to the lsp parser though, it made a big difference in usability.
yup. my macro-defs were able to treat a call to a symbol like
{clojure.spec.alpha/def [:declaration :element]}
and I would tag the first arg (the keyword) as a :declaration
. Kondo obviously has a different model.
So it would be useful to have this in hooks then I guess.
(reg-keyword-def ....)
or somethingin the only one usage lint model, I think you'd have troubles if someone simply forgets to s/def
so if the user just (->> x (s/validate ::a) (s/conform ::a))
it's moving to a very heuristic approach.
I haven't looked at hooks much, but yeah, that might do it. It could add an extra flag to :keywords
or we could do a similar split of usages/definitions that vars use
ok, we can think more about the name, but I think we have an idea what should be done now
could be reg-by set, in the code I've run into it hasn't been a concern but it's a good question.
Maybe :registered-by clojure.spec.alpha works. :registered-by clojure.core/add-watch
yeah, we can do :def <truthy>
where truthy is either a boolean or the macro / fn that registered it
@U0BUV7XSA Since this analysis isn't fully formed in our heads yet, I think the best way forward is to add it as discussed and also write some docs, but comment out the docs (using HTML comments) so it's only private to both clj-kondo and clojure-lsp for now, so we have freedom to make changes until we are satisfied.
Let me think through the use cases here... sorry if I'm repeating what you both discussed already. It seems the benefit here is to be able to do go to definition
. There is also find reference
, but I think that's covered just by having analysis return all keywords and where they are found correct?
For go-to-definition
we need to know which of the many places the keyword is used in its canonical definition
. Figuring out what is canonical is the hard part. Ya, I can imagine if you can teach clj-kondo what functions or macro forms where a keyword appear is the "canonical definition" for it. That would be neat actually.
Like @borkdude said, its possible more than one thing define things with the same keyword. So I'm guessing it needs to be each keyword can have 0 or more definitions. And the editor needs to support listing multiple choices when you do go to definition
. I can do that in Anakondo easily, not sure if LSP supports this?
Now there's the issue of when things are defined dynamically, so now where do you go? Can clj-kondo be smart enough to learn about those as well, and give us the place where the keyword will get dynamically defined? That's what you meant by supporting hooks
for it @borkdude?
And a nice linter for this would be that say if you are inside of a spec function, and you use a keyword that is not defined anywhere it warns.
Something else I'm thinking, on a similar vein, it be nice if when auto-completing keywords, it could show only keywords that are relevant. So if you s/def
some keyword, and then you s/validate
like only keywords that have been s/def
should show up in the auto-completion. Just thinking how to do this...
Also, why not make it a separate entry like var-definitions? Maybe I understood wrong, but it sounded here you wanted on keyword-usages to add a :def
key?
I think the reasoning for keeping it all in a single entry is because the interpretation of a keyword as a def is up to userspace.
> And the editor needs to support listing multiple choices when you do `go to definition`. I can do that in Anakondo easily, not sure if LSP supports this? The protocol seems to allow for multiple definition results it will depend on the clients, as always, to support it.
> I think the reasoning for keeping it all in a single entry is because the interpretation of a keyword as a def is up to userspace. Ok, but I mean we're talking in either case, userspace needs to configure clj-kondo for it no? But, ya ok, I see the point, like it might look weird for the map to be empty most of the time, unless userspace configured some keyword defs
I guess from a consumer perspective, it seems like you'd have a different way to parse symbols from keywords for "go to def" and "find references". Which is why I was wondering like if that made sense.
@U0K064KQV the idea is that a keyword in and of itself doesn't mean anything. e.g.:
(defn foo [x])
(foo :bar)
;; vs
(defn reg-something [k])
(reg-something :bar)
are analogous, whereas
(def x 1)
has definition meaning regardless of what semantics users give to their functionsThis is something that isn't fully clear to me yet, that's why I propose keeping this analysis output private until it's been used by LSP for a while
we could make them separate, but having a list of all occurring keywords seems useful to have anyway for renaming purposes
I feel like either or, we're still adding the concept of keywords can be considered as having a definition no? Even though by default they don't in Clojure, but then per-library or user usage they might. I feel for me its more what's easiest to parse in the analysis structure for the common use. I guess its a small detail, either or you can parse out the information. But say someone runs: "go to definition" on a keyword, now you'd filter through the keyword usage for that keyword for the one with "def = true".
Actually, sorry, maybe that's more a general critique. I remember now. When I implemented anakondo, I think I wished that things were pivoted, instead of vector of maps, it be maps of vectors of maps
I found needed to actually parse everything back into maps indexed on names for the different constructs.
> I feel for me its more what's easiest to parse in the analysis structure for the common use. That's a good point
@U0K064KQV the reason you get everything as a list is that it's not always clear what the index should be. either clj-kondo does this for you (and has to guess what is the right format), or you do it yourself. if clj-kondo would do this for you and you wanted a different index, then it would be hairy to go from one index to another. the work has to happen somewhere and it's most flexible if that somewhere is the end user
people might also want to index on filename, for example, or namespace and then name, etc
Ya, that's true. Actually I can't remember fully, but I probably did end up indexing in a slightly more custom to my needs way.
I think had it been in Clojure, I wouldn't have feel the pain 😛, but transforming datastructures in Emacs Lisp is not as simple haha
Ya, I've been considering it. The thing is, with any separate process option, now I need to figure out a protocol to communicate between the editor and that, and the editor needs to manage a long running process, maybe multiple if you have multiple project, etc. And I think there's a lot of brittleness there, and also I'm just re-inventing LSP at this point.
we could even let clj-kondo take some processing function that is executed using sci, since it already has sci
So you could use Clojure to re-structure it however is best for your use case, and then your editor gets a JSON in that new structure.
I think the function could be applied to the :findings
as well, just one function that gets the end result before it's written to EDN or JSON
Ok, well, it would be cool. I don't know if I'd say its at the top of the Wishlist though 😛 Even though I'm here complaining about how painful it is to do in Emacs Lisp, that's pain I chose for myself haha
The only thing I can give props to Emacs Lisp, its use of memory is quite impressive, like its very memory efficient