Skip to content

Commit bc269d5

Browse files
xiongtxbbatsov
authored andcommitted
[Fix #409] Include special forms in apropos (#410)
Fixes #409. Some special forms (e.g. `if`) are included in apropos if the `search-ns` is nil or `clojure.core`. Included special forms are those those with documentation in `clojure.repl`, as well as `&`, `catch`, and `finally`, the first of which uses the documentation of `fn` and the others that of `try`.
1 parent ec4de41 commit bc269d5

File tree

4 files changed

+81
-17
lines changed

4 files changed

+81
-17
lines changed

src/cider/nrepl/middleware/apropos.clj

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
(:require [cider.nrepl.middleware.util.error-handling :refer [with-safe-transport]]
55
[clojure.string :as str]
66
[clojure.tools.nrepl.middleware :refer [set-descriptor!]]
7+
[cider.nrepl.middleware.info :as info]
78
[cider.nrepl.middleware.util.namespace :as ns]))
89

910
;;; ## Overview
@@ -17,17 +18,26 @@
1718
;; abbreviated version (i.e. first sentence only) may be returned for
1819
;; symbol-only searches.
1920

21+
(def special-forms
22+
"Special forms that can be apropo'ed."
23+
(concat (keys (var-get #'clojure.repl/special-doc-map))
24+
'[& catch finally]))
25+
2026
(defn var-name
21-
"Return a var's namespace-qualified name as a string."
27+
"Return a special form's name or var's namespace-qualified name as a string."
2228
[v]
23-
(str/join "/" ((juxt (comp ns-name :ns) :name)
24-
(meta v))))
29+
(if (special-symbol? v)
30+
(str (:name (info/resolve-special v)))
31+
(str/join "/" ((juxt (comp ns-name :ns) :name) (meta v)))))
2532

2633
(defn var-doc
27-
"Return a var's docstring, optionally limiting the number of sentences
28-
returned."
34+
"Return a special form or var's docstring, optionally limiting the number of
35+
sentences returned."
2936
([v]
30-
(or (:doc (meta v)) "(not documented)"))
37+
(or (if (special-symbol? v)
38+
(:doc (info/resolve-special v))
39+
(:doc (meta v)))
40+
"(not documented)"))
3141
([n v]
3242
(->> (-> (var-doc v)
3343
(str/replace #"\s+" " ") ; normalize whitespace
@@ -70,12 +80,16 @@
7080
regex (-> (if case-sensitive? query (format "(?i:%s)" query)) re-pattern)]
7181
(->> (namespaces ns search-ns filter-regexps)
7282
(mapcat (comp (partial sort-by var-name) vals ns-vars))
83+
(concat (when (or (empty? search-ns)
84+
(= 'clojure.core (symbol search-ns)))
85+
special-forms))
7386
(filter (comp (partial re-find regex) search-prop))
7487
(map (fn [v] {:name (var-name v)
7588
:doc (var-doc* v)
76-
:type (cond (:macro (meta v)) :macro
77-
(fn? (deref v)) :function
78-
:else :variable)})))))
89+
:type (cond (special-symbol? v) :special-form
90+
(:macro (meta v)) :macro
91+
(fn? (deref v)) :function
92+
:else :variable)})))))
7993

8094
;;; ## Middleware
8195

src/cider/nrepl/middleware/info.clj

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,17 @@
139139
used by `clojure.repl/doc`."
140140
[sym]
141141
(try
142-
(let [sym (get '{& fn, catch try, finally try} sym sym)
142+
(let [orig-sym sym
143+
sym (get '{& fn, catch try, finally try} sym sym)
143144
v (meta (ns-resolve (find-ns 'clojure.core) sym))]
144145
(when-let [m (cond (special-symbol? sym) (#'repl/special-doc sym)
145146
(:special-form v) v)]
146-
(assoc m
147-
:url (if (contains? m :url)
148-
(when (:url m)
149-
(str "https://clojure.org/" (:url m)))
150-
(str "https://clojure.org/special_forms#" (:name m))))))
147+
(-> m
148+
(assoc :name orig-sym)
149+
(assoc :url (if (contains? m :url)
150+
(when (:url m)
151+
(str "https://clojure.org/" (:url m)))
152+
(str "https://clojure.org/special_forms#" (:name m)))))))
151153
(catch NoClassDefFoundError _)
152154
(catch Exception _)))
153155

test/clj/cider/nrepl/middleware/apropos_test.clj

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,28 @@
22
(:require [cider.nrepl.middleware.apropos :refer :all]
33
[clojure.test :refer :all]
44
[cider.nrepl.test-session :as session]
5+
[clojure.repl :as repl]
56
[clojure.string :as str]))
67

78
(def ^{:doc "Test1. Test2. Test3."} public-var [1 2 3])
89
(def ^:private ^{:doc "Can't. See. Me"} private-var [:a :b :c])
910

11+
(deftest var-name-test
12+
(testing "Returns Var's namespace-qualified name"
13+
(is (= "clojure.core/conj" (var-name #'clojure.core/conj))))
14+
15+
(testing "Returns special form's name"
16+
(is (= "if" (var-name 'if)))))
17+
18+
(deftest var-doc-test
19+
(testing "Returns Var's doc"
20+
(is (= (:doc (meta #'clojure.core/conj))
21+
(var-doc #'clojure.core/conj))))
22+
23+
(testing "Returns special form's doc"
24+
(is (= (:doc (#'repl/special-doc 'if))
25+
(var-doc 'if)))))
26+
1027
(deftest unit-test-metadata
1128
(is (= (var-name #'public-var) "cider.nrepl.middleware.apropos-test/public-var"))
1229
(is (= (var-doc #'public-var) "Test1. Test2. Test3."))
@@ -52,7 +69,22 @@
5269
"Symbol search should return an abbreviated docstring.")
5370
(is (= (take 20 (:doc x))
5471
(take 20 (:doc y)))
55-
"The abbreviated docstring should be the start of the full docstring."))))
72+
"The abbreviated docstring should be the start of the full docstring.")))
73+
74+
(testing "Includes special forms when `search-ns` is nil"
75+
(is (not-empty (filter #(= "if" (:name %))
76+
(find-symbols nil "if" nil
77+
false false false nil)))))
78+
79+
(testing "Includes special forms when `search-ns` is \"clojure.core\""
80+
(is (not-empty (filter #(= "if" (:name %))
81+
(find-symbols nil "if" "clojure.core"
82+
false false false nil)))))
83+
84+
(testing "Excludes special forms when `search-ns` is some other ns"
85+
(is (empty? (filter #(= "if" (:name %))
86+
(find-symbols nil "if" "clojure.set"
87+
false false false nil))))))
5688

5789
(use-fixtures :each session/session-fixture)
5890
(deftest integration-test

test/clj/cider/nrepl/middleware/info_test.clj

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
(spit tmp-file-path "test")
5454
(testing "when fake.class.path is not set"
5555
(is (not (= (class (file tmp-file-name))
56-
java.net.URL)))
56+
java.net.URL)))
5757
(is (= (file tmp-file-name) tmp-file-name)))
5858
(testing "when fake.class.path is set"
5959
(try
@@ -73,6 +73,22 @@
7373
(finally
7474
(System/clearProperty "fake.class.path")))))))
7575

76+
(deftest resolve-special-test
77+
(testing "Resolves all special forms"
78+
(let [specials (keys clojure.lang.Compiler/specials)]
79+
(is (every? (fn [[sym {:keys [name special-form]}]]
80+
(and (= sym name)
81+
(true? special-form)))
82+
(map #(vector % (info/resolve-special %)) specials)))))
83+
84+
(testing "Names are correct for symbols #{&, catch, finally}"
85+
(is (= '& (:name (info/resolve-special '&))))
86+
(is (= 'catch (:name (info/resolve-special 'catch))))
87+
(is (= 'finally (:name (info/resolve-special 'finally)))))
88+
89+
(testing "Returns nil for unknown symbol"
90+
(is (nil? (info/resolve-special 'unknown)))))
91+
7692
(deftype T [])
7793

7894
(deftest info-test

0 commit comments

Comments
 (0)