diff --git a/CHANGELOG.md b/CHANGELOG.md index be3aad1a8..c82e6e961 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## master (unreleased) +### New features + +* Observe `cider-doc.edn` Java resource files for user-extensible documentation. + ### Changes * Refine `ops-that-can-eval` internals, adapting them to the new `cider.nrepl.middleware.reload` ops. diff --git a/doc/modules/ROOT/pages/nrepl-api/ops.adoc b/doc/modules/ROOT/pages/nrepl-api/ops.adoc index 03fc00894..62c5e2783 100644 --- a/doc/modules/ROOT/pages/nrepl-api/ops.adoc +++ b/doc/modules/ROOT/pages/nrepl-api/ops.adoc @@ -316,7 +316,8 @@ For Java interop queries, it helps inferring the precise type of the object the making the results more accurate (and less numerous). * `:member` A Java class member. * `:ns` The current namespace -* `:sym` The symbol to lookup +* `:sym` The symbol to lookup. Must be a string. If it represents a keyword, please express so via the ``:symbol-type`` param. +* `:symbol-type` The type of object of ``:sym`` as seen by the user. One of: "symbol" (default), "keyword", "string". * `:var-meta-allowlist` The metadata keys from vars to be returned. Currently only affects ``:clj``. Defaults to the value of ``orchard.meta/var-meta-allowlist``. If specified, the value will be concatenated to that of ``orchard.meta/var-meta-allowlist``. @@ -453,7 +454,8 @@ For Java interop queries, it helps inferring the precise type of the object the making the results more accurate (and less numerous). * `:member` A Java class member. * `:ns` The current namespace -* `:sym` The symbol to lookup +* `:sym` The symbol to lookup. Must be a string. If it represents a keyword, please express so via the ``:symbol-type`` param. +* `:symbol-type` The type of object of ``:sym`` as seen by the user. One of: "symbol" (default), "keyword", "string". * `:var-meta-allowlist` The metadata keys from vars to be returned. Currently only affects ``:clj``. Defaults to the value of ``orchard.meta/var-meta-allowlist``. If specified, the value will be concatenated to that of ``orchard.meta/var-meta-allowlist``. diff --git a/src/cider/nrepl.clj b/src/cider/nrepl.clj index ebae1a7d7..32ff84c76 100644 --- a/src/cider/nrepl.clj +++ b/src/cider/nrepl.clj @@ -272,7 +272,7 @@ Depending on the type of the return value of the evaluation this middleware may "doc-block-tags-fragments" (str "May be absent. Represent the 'param', 'returns' and 'throws' sections a Java doc comment. " fragments-desc)}) (def info-params - {"sym" "The symbol to lookup" + {"sym" "The symbol to lookup. Must be a string. If it represents a keyword, please express so via the `:symbol-type` param." "ns" "The current namespace" "context" "A Compliment completion context, just like the ones already passed for the \"complete\" op, with the difference that the symbol at point should be entirely replaced by \"__prefix__\". @@ -280,6 +280,7 @@ For Java interop queries, it helps inferring the precise type of the object the making the results more accurate (and less numerous)." "class" "A Java class. If `:ns` is passed, it will be used for fully-qualifying the class, if necessary." "member" "A Java class member." + "symbol-type" "The type of object of `:sym` as seen by the user. One of: \"symbol\" (default), \"keyword\", \"string\"." "var-meta-allowlist" "The metadata keys from vars to be returned. Currently only affects `:clj`. Defaults to the value of `orchard.meta/var-meta-allowlist`. If specified, the value will be concatenated to that of `orchard.meta/var-meta-allowlist`."}) diff --git a/src/cider/nrepl/middleware/info.clj b/src/cider/nrepl/middleware/info.clj index a3a669e47..a161793c0 100644 --- a/src/cider/nrepl/middleware/info.clj +++ b/src/cider/nrepl/middleware/info.clj @@ -1,11 +1,12 @@ (ns cider.nrepl.middleware.info (:require - [compliment.context] - [compliment.sources.class-members] [cider.nrepl.middleware.util :as util] [cider.nrepl.middleware.util.cljs :as cljs] [cider.nrepl.middleware.util.error-handling :refer [with-safe-transport]] + [clojure.edn :as edn] [clojure.string :as str] + [compliment.context] + [compliment.sources.class-members] [orchard.eldoc :as eldoc] [orchard.info :as info] [orchard.meta :as meta] @@ -78,9 +79,51 @@ (def var-meta-allowlist-set (set meta/var-meta-allowlist)) +(def DSLable? + (some-fn simple-symbol? ;; don't allow ns-qualified things, since the Clojure var system takes precedence over DSLs + simple-keyword? + string?)) + +(defn cider-doc-edn-configs [] + (let [resources (-> (Thread/currentThread) + (.getContextClassLoader) + (.getResources "cider-doc.edn") + (enumeration-seq) + (distinct))] + (into {} + (keep (fn [resource] + (try + (let [m (into {} + (keep (fn [[k v]] + (let [symbols (into #{} + (filter DSLable?) + k) + resolved (into {} + (map (fn [[kk vv]] + [kk (or (misc/require-and-resolve vv) + (throw (ex-info "Discard" {})))])) + v)] + (when (and (contains? resolved :info-provider) + (contains? resolved :if) + (seq symbols)) + [symbols + resolved])))) + (edn/read-string (slurp resource)))] + (when (seq m) + ;; We don't merge all configs into a single object, because that risks data loss + ;; (e.g. if we merge {[foo] ,,,} with {[foo] ,,,}), one [foo] ,,, entry would be lost. + ;; Which is why we use `(str resource)` to keep an extra level of nesting. + [(str resource) + m])) + (catch Exception e ;; discard unparseable/unloadable user input + nil)))) + resources))) + (defn info - [{:keys [ns sym class member context var-meta-allowlist] + [{:keys [ns sym class member context var-meta-allowlist file] + symbol-type :type ;; one of: "symbol", "keyword", "string". Represents whether the queried token is a symbol/keyword/string. legacy-sym :symbol + :or {symbol-type "symbol"} :as msg}] (let [sym (or (not-empty legacy-sym) (not-empty sym)) @@ -116,11 +159,33 @@ (when var-meta-allowlist {:var-meta-allowlist (into meta/var-meta-allowlist (remove var-meta-allowlist-set) - var-meta-allowlist)}))] + var-meta-allowlist)})) + match-from-configs (when (and (not java?) ;; We don't encourage users to create ambiguity over Java interop syntax + ;; We don't encourage users to create ambiguity over Clojure var syntax, + ;; so ns-qualified symbols are disregarded: + (DSLable? sym)) + (some (fn [[_resource config]] + (some (fn [[symbols rules]] + (and (contains? symbols sym) + ((:if rules) context) + ((:info-provider rules) {:symbol (cond + (= symbol-type "symbol") + (symbol sym) + + (= symbol-type "keyword") + (keyword sym) + + :else sym) + :ns ns + :file file + :context context}))) + config)) + (cider-doc-edn-configs)))] (cond - java? (info/info-java class (or member sym)) - (and ns sym) (info/info* info-params) - :else nil))) + java? (info/info-java class (or member sym)) + match-from-configs match-from-configs + (and ns sym) (info/info* info-params) + :else nil))) (defn info-reply [msg] diff --git a/test/clj/cider/nrepl/middleware/info_test.clj b/test/clj/cider/nrepl/middleware/info_test.clj index d1829f6fb..50b15ead0 100644 --- a/test/clj/cider/nrepl/middleware/info_test.clj +++ b/test/clj/cider/nrepl/middleware/info_test.clj @@ -12,6 +12,27 @@ (cider.nrepl.test AnotherTestClass TestClass YetAnotherTest) (org.apache.commons.lang3 SystemUtils))) +(defn sample-info-provider [{:keys [symbol ns file context]}] + {:ns ns + :name symbol + :doc (format "%s rocks - doc dynamically generated from %s" + symbol + (-> ::_ namespace)) + :file file + :arglists [] + ;; :forms + :macro false + :special-form false + :protocol false + ;; :line + ;; :column + :static false + :added "1.0" + :deprecated false}) + +(defn sample-info-provider? [{:keys [symbol ns file context]}] + true) + (defprotocol FormatResponseTest (proto-foo [this]) (proto-bar [this] "baz")) @@ -370,6 +391,12 @@ (let [response (session/message {:op "info" :sym "xyz"})] (is (nil? (:see-also response)) + (pr-str response)))) + + (testing "cider-doc.edn" + (let [response (session/message {:op "info" :sym "cider-doc-edn-example"})] + (is (= "cider-doc-edn-example rocks - doc dynamically generated from cider.nrepl.middleware.info-test" + (:doc response)) (pr-str response))))) (testing "eldoc op" diff --git a/test/resources/cider-doc.edn b/test/resources/cider-doc.edn new file mode 100644 index 000000000..e6be4c0bc --- /dev/null +++ b/test/resources/cider-doc.edn @@ -0,0 +1,2 @@ +{[cider-doc-edn-example] {:info-provider cider.nrepl.middleware.info-test/sample-info-provider + :if cider.nrepl.middleware.info-test/sample-info-provider?}}