From 218d6e9843f8c2b73627a7262331a4206ae1cbcd Mon Sep 17 00:00:00 2001 From: vemv Date: Sun, 6 Aug 2023 14:41:24 +0200 Subject: [PATCH] `orchard.xref`: include info for test vars Fixes https://github.com/clojure-emacs/orchard/issues/176 --- CHANGELOG.md | 5 ++++ src/orchard/xref.clj | 37 +++++++++++++++++++------ test/orchard/xref_test.clj | 57 +++++++++++++++++++++++++++++--------- 3 files changed, 78 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f81b7610..4e279a70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## master (unreleased) +### Changes + +- [#176](https://github.com/clojure-emacs/orchard/issues/176): `orchard.xref`: include info for test vars. +- `orchard.xref`: avoid duplicate vars that might appear following REPL re-evaluation. + ## 0.14.1 (2023-08-05) ### Changes diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index 4663e2b3..eb567e74 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -8,12 +8,26 @@ [clojure.string :as string] [orchard.query :as q])) +(defn- var->symbol + "Normally one could just use `(symbol var-ref)`, + but that doesn't work in older Clojures." + [var-ref] + (let [{:keys [ns name]} (meta var-ref)] + (symbol (str (ns-name ns)) + (str name)))) + +(defn var->fn [var-ref] + (let [{:keys [test]} (meta var-ref)] + (if (fn? test) + test ;; for deftests, :test metadata contains the actual test implementation, with all the interesting contents. + (var-get var-ref)))) + (defn- to-fn "Convert `thing` to a function value." [thing] (cond - (var? thing) (var-get thing) - (symbol? thing) (var-get (find-var thing)) + (var? thing) (var->fn thing) + (symbol? thing) (var->fn (find-var thing)) (fn? thing) thing)) (defn- fn-name [^java.lang.Class f] @@ -71,12 +85,19 @@ (map (fn [[_k value]] (.get ^java.lang.ref.Reference value))) (mapcat fn-deps-class)) - class-cache)] - ;; if there's no deps the class is most likely AoT compiled, - ;; try to access it directly - (if (empty? deps) - (-> v .getClass fn-deps-class) - deps)))) + class-cache) + result + ;; if there's no deps the class is most likely AoT compiled, + ;; try to access it directly + (if (empty? deps) + (-> v .getClass fn-deps-class) + deps)] + (into #{} + (map resolve) ;; choose the freshest one + ;; group duplicates. This is important + ;; because there can be two seemingly equal #'foo.bar/baz var objects in the result. + ;; That can happen as one re-evaluates code and the old var hasn't been GC'd yet. + (keys (group-by var->symbol result)))))) (defn fn-transitive-deps "Returns a set with all the functions invoked inside `v` or inside those functions. diff --git a/test/orchard/xref_test.clj b/test/orchard/xref_test.clj index 24341a83..57cb7ca4 100644 --- a/test/orchard/xref_test.clj +++ b/test/orchard/xref_test.clj @@ -12,6 +12,11 @@ (defn- dummy-fn [_x] (map #(fn-dep % 2) (filter even? (range 1 10)))) +;; Supports #'fn-deps-test +(deftest sample-test + (is (some? xref/eval-lock)) + (is (some? (xref/fn-refs #'dummy-fn)))) + (deftest fn-deps-test (testing "with a fn value" (is (= #{#'clojure.core/map #'clojure.core/filter @@ -23,6 +28,17 @@ #'clojure.core/even? #'clojure.core/range #'orchard.xref-test/fn-dep} (xref/fn-deps #'dummy-fn)))) + (testing "with a var that backs a deftest" + (is (= #{#'orchard.xref-test/dummy-fn + #'orchard.xref/eval-lock + #'clojure.test/do-report + #'clojure.core/cons + #'clojure.core/some? + #'clojure.core/apply + #'orchard.xref/fn-refs + #'clojure.core/list} + (xref/fn-deps #'sample-test)))) + (testing "with a symbol" (is (= #{#'clojure.core/map #'clojure.core/filter #'clojure.core/even? #'clojure.core/range #'orchard.xref-test/fn-dep} @@ -41,19 +57,6 @@ (def yyy (symbol (str (gensym)) (str (gensym)))) -(deftest fn-refs-test - (testing "with a fn value" - (is (= '() (xref/fn-refs dummy-fn))) - (is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn))) - (testing "with a var" - (is (= '() (xref/fn-refs #'dummy-fn))) - (is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn))) - (testing "with a symbol" - (is (= '() (xref/fn-refs 'orchard.xref-test/dummy-fn))) - (is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn))) - (testing "that usage from inside an anonymous function is found" - (is (contains? (into #{} (xref/fn-refs #'fn-dep)) #'orchard.xref-test/dummy-fn)))) - (deftest fn-transitive-deps-test (testing "basics" (let [expected #{#'orchard.xref-test/fn-deps-test #'orchard.xref-test/fn-dep #'clojure.core/even? @@ -67,3 +70,31 @@ it gets found because its a non `:static` dependancy.") (is (= expected (xref/fn-transitive-deps dummy-fn)))))) + +(deftest fn-refs-test + (testing "with a fn value" + (is (= #{#'orchard.xref-test/fn-deps-test + #'orchard.xref-test/fn-transitive-deps-test + #'orchard.xref-test/sample-test + #'orchard.xref-test/fn-refs-test} + (set (xref/fn-refs dummy-fn)))) + (is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn))) + + (testing "with a var" + (is (= #{#'orchard.xref-test/fn-deps-test + #'orchard.xref-test/fn-transitive-deps-test + #'orchard.xref-test/sample-test + #'orchard.xref-test/fn-refs-test} + (set (xref/fn-refs #'dummy-fn)))) + (is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn))) + + (testing "with a symbol" + (is (= #{#'orchard.xref-test/fn-deps-test + #'orchard.xref-test/fn-transitive-deps-test + #'orchard.xref-test/sample-test + #'orchard.xref-test/fn-refs-test} + (set (xref/fn-refs 'orchard.xref-test/dummy-fn)))) + (is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn))) + + (testing "that usage from inside an anonymous function is found" + (is (contains? (into #{} (xref/fn-refs #'fn-dep)) #'orchard.xref-test/dummy-fn))))