formatting-stack
formatting-stack copied to clipboard
Namespaced keyword usage linter
Context
Some codebases use ns-qualifed keywords heavily, including cross-ns keyword references.
Such a pattern is subject to bugs:
- Consumers making typos
- Producers renaming keywords
Task
Create a linter that fails when consumed keywords don't exist in the producer side.
How would one determine the 'existence' of a keyword in a certain ns? Just as a spec (using #'clojure.spec.alpha/get-spec for example)?
No, I wouldn't couple the linter to Spec in any way
Given a ns reference such as a.b.c, one can obtain its source non-mutatively (i.e. no require performed, which is a side-effect) with this snippet.
Armed with that source, one can perform a simple scan of keywords with tools.reader.
played around with this a bit. The bit from file -> keywords.
(->> (str "[" (slurp source) "]")
(tools.reader/read-string {:read-cond :preserve
:features #{:clj :cljs}})
(flatten)
(filter #(and (keyword? %)
(= (str *ns*)
(namespace %))))
(map name))
=>
'("local-name"
"binding-form"
"local-name"
"seq-binding-form"
"map-binding-form"
"seq-binding-form"
"binding-form"
...)
still need to figure out how to resolve :clojure.core.specs.alpha/rename -> ["clojure/core/specs/alpha.clj", :rename].
still need to figure out how to resolve :clojure.core.specs.alpha/rename -> ["clojure/core/specs/alpha.clj", :rename].
I guess part of the difficuly lies in that you cannot know in advance if the filename is .clj, .cljs, .cljc or multiple of those; particularly without performing a require (a limitation which I'd find clean).
Perhaps one can try the three extensions sequentially, build a set out of each maybe-file, and finally merge those sets.