From a5301ca98a75ab26b1893298eeda8b2c4400895d Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Sun, 2 Jan 2022 22:47:28 +0100 Subject: [PATCH 01/18] fn-deps for lambdas --- src/orchard/xref.clj | 123 ++++++++++++++++++++++++++++++++++--- test/orchard/xref_test.clj | 16 +++-- 2 files changed, 127 insertions(+), 12 deletions(-) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index c11dfb90..4edf98f1 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -3,8 +3,48 @@ references." {:added "0.5"} (:require + [clojure.tools.reader :as r] + [clojure.tools.reader.reader-types :as rts] + [clojure.repl :as repl] [orchard.query :as q])) +;;;;;;; taken from sayid, needs to be cleaned up +(defn- mk-dummy-whitespace + [lines cols] + (apply str + (concat (repeat lines "\n") + (repeat cols " ")))) + +(defn- mk-positionalble-src-logging-push-back-rdr + [s file line col] + (rts/source-logging-push-back-reader (str (mk-dummy-whitespace (dec line) ;;this seem unfortunate + (dec col)) + s) + (+ (count s) line col 1) + file)) + +(defn- hunt-down-source + [fn-sym] + (let [{:keys [source file line column]} (-> fn-sym + resolve + meta)] + ;; (println file) + (or source + (and file (r/read (mk-positionalble-src-logging-push-back-rdr + (or + (clojure.repl/source-fn fn-sym) + (->> file + slurp + clojure.string/split-lines + (drop (dec line)) + (clojure.string/join "\n")) + "nil") + file + line + column)))))) + +;;;;;;; taken from sayid, needs to be cleaned up + (defn- as-val "Convert `thing` to a function value." [thing] @@ -13,21 +53,77 @@ (symbol? thing) (var-get (find-var thing)) (fn? thing) thing)) -(defn fn-deps - "Returns a set with all the functions invoked by `val`. - `val` can be a function value, a var or a symbol." - {:added "0.5"} +(defn- f->sym [f] + (-> f .getClass .getName repl/demunge symbol)) + +(defn- fn-source [f] + (hunt-down-source (f->sym f))) + +(defn- fn-name [f] + (-> f .getName repl/demunge symbol)) + +(defonce classbytes (atom {})) + +(defn- recompile [ns-sym form] + (push-thread-bindings + {clojure.lang.Compiler/LOADER + (proxy [clojure.lang.DynamicClassLoader] [@clojure.lang.Compiler/LOADER] + (defineClass + ([name bytes src] + (swap! classbytes assoc name bytes) + (proxy-super defineClass name bytes src))))}) + (try + (let [line @clojure.lang.Compiler/LINE + column @clojure.lang.Compiler/COLUMN + line (if-let [line (:line (meta form))] + line + line) + column (if-let [column (:column (meta form))] + column + column)] + (push-thread-bindings {clojure.lang.Compiler/LINE line + clojure.lang.Compiler/COLUMN column}) + (try + (let [form (macroexpand form)] + (binding [*ns* (create-ns ns-sym)] + (clojure.lang.Compiler/analyze + clojure.lang.Compiler$C/EVAL + (nth form 2)))) + (finally + (pop-thread-bindings)))) + (finally + (pop-thread-bindings)))) + + + +(defn- fn-deps-class [val] - (let [val (as-val val)] - (set (some->> val class .getDeclaredFields + (let [v (if (class? val) + val + (eval val))] + (set (some->> v .getDeclaredFields (keep (fn [^java.lang.reflect.Field f] (or (and (identical? clojure.lang.Var (.getType f)) (java.lang.reflect.Modifier/isPublic (.getModifiers f)) (java.lang.reflect.Modifier/isStatic (.getModifiers f)) (-> f .getName (.startsWith "const__")) - (.get f val)) + (.get f (fn-name v))) nil))))))) +(defn fn-deps + "Returns a set with all the functions invoked by `val`. + `val` can be a function value, a var or a symbol." + {:added "0.5"} + [f] + (reset! classbytes {}) + (let [f (as-val f)] + (when-let [source (and (fn? f) (fn-source f))] + (let [sym (f->sym f) + b (do (recompile (-> sym namespace symbol) source) @classbytes) + class-names (map first b) + deps (set (mapcat #(-> % symbol fn-deps-class) class-names))] + deps)))) + (defn- fn->sym "Convert a function value `f` to symbol." [f] @@ -50,3 +146,16 @@ all-vars (q/vars {:ns-query {:project? true} :private? true}) deps-map (zipmap all-vars (map fn-deps all-vars))] (map first (filter (fn [[_k v]] (contains? v var)) deps-map)))) + +(defn- dummy-fn [_x] + (map #(* % 2) (filter even? (range 1 10)))) + +(comment + + (fn-deps #'user/jdk8?) + (fn-deps #'orchard.meta/ns-file) + (def vars (q/vars {:ns-query {:project? true} :private? true})) + (map fn-deps vars) + (drop 100 vars) + (when-let [x (fn? (as-val user/jdk8?))] + "some")) \ No newline at end of file diff --git a/test/orchard/xref_test.clj b/test/orchard/xref_test.clj index a7c1c46d..3f57368a 100644 --- a/test/orchard/xref_test.clj +++ b/test/orchard/xref_test.clj @@ -3,22 +3,25 @@ [clojure.test :refer [deftest is testing]] [orchard.xref :as xref])) +(defn- times [a b] + (* a b)) + (defn- dummy-fn [_x] - (map #(* % 2) (filter even? (range 1 10)))) + (map #(times % 2) (filter even? (range 1 10)))) (deftest fn-deps-test (testing "with a fn value" (is (= (xref/fn-deps dummy-fn) #{#'clojure.core/map #'clojure.core/filter - #'clojure.core/even? #'clojure.core/range}))) + #'clojure.core/even? #'clojure.core/range #'orchard.xref-test/times}))) (testing "with a var" (is (= (xref/fn-deps #'dummy-fn) #{#'clojure.core/map #'clojure.core/filter - #'clojure.core/even? #'clojure.core/range}))) + #'clojure.core/even? #'clojure.core/range #'orchard.xref-test/times}))) (testing "with a symbol" (is (= (xref/fn-deps 'orchard.xref-test/dummy-fn) #{#'clojure.core/map #'clojure.core/filter - #'clojure.core/even? #'clojure.core/range})))) + #'clojure.core/even? #'clojure.core/range #'orchard.xref-test/times})))) ;; The mere presence of this var can reproduce a certain issue. See: ;; https://github.com/clojure-emacs/orchard/issues/135#issuecomment-939731698 @@ -38,4 +41,7 @@ (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)))) + (is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn))) + (testing "with a lambda" + (is (= (xref/fn-refs dummy-fn) '())) + (is (contains? (into #{} (xref/fn-refs #'times)) #'orchard.xref-test/dummy-fn)))) From 93459a0d31b199ac286fce112b0a3288c9ccdfd6 Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Mon, 3 Jan 2022 18:30:13 +0100 Subject: [PATCH 02/18] fixed reader dependency --- src/orchard/xref.clj | 66 ++++++++++++-------------------------- test/orchard/xref_test.clj | 1 - 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index 4edf98f1..24b1844c 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -3,47 +3,25 @@ references." {:added "0.5"} (:require - [clojure.tools.reader :as r] - [clojure.tools.reader.reader-types :as rts] [clojure.repl :as repl] [orchard.query :as q])) -;;;;;;; taken from sayid, needs to be cleaned up -(defn- mk-dummy-whitespace - [lines cols] - (apply str - (concat (repeat lines "\n") - (repeat cols " ")))) - -(defn- mk-positionalble-src-logging-push-back-rdr - [s file line col] - (rts/source-logging-push-back-reader (str (mk-dummy-whitespace (dec line) ;;this seem unfortunate - (dec col)) - s) - (+ (count s) line col 1) - file)) - (defn- hunt-down-source [fn-sym] (let [{:keys [source file line column]} (-> fn-sym resolve meta)] - ;; (println file) - (or source - (and file (r/read (mk-positionalble-src-logging-push-back-rdr - (or - (clojure.repl/source-fn fn-sym) - (->> file - slurp - clojure.string/split-lines - (drop (dec line)) - (clojure.string/join "\n")) - "nil") - file - line - column)))))) - -;;;;;;; taken from sayid, needs to be cleaned up + (try (or source + (and file (read-string {:read-cond :allow} + (or + (clojure.repl/source-fn fn-sym) + (->> file + slurp + clojure.string/split-lines + (drop (dec line)) + (clojure.string/join "\n")) + "nil")))) + (catch Exception ex)))) ;; explodes on namespaced keywords (defn- as-val "Convert `thing` to a function value." @@ -85,17 +63,16 @@ clojure.lang.Compiler/COLUMN column}) (try (let [form (macroexpand form)] - (binding [*ns* (create-ns ns-sym)] - (clojure.lang.Compiler/analyze - clojure.lang.Compiler$C/EVAL - (nth form 2)))) + (when (and (coll? form) (= 'clojure.core/fn (first (nth form 2 nil)))) + (binding [*ns* (create-ns ns-sym)] + (clojure.lang.Compiler/analyze + clojure.lang.Compiler$C/EVAL + (nth form 2))))) (finally (pop-thread-bindings)))) (finally (pop-thread-bindings)))) - - (defn- fn-deps-class [val] (let [v (if (class? val) @@ -151,11 +128,10 @@ (map #(* % 2) (filter even? (range 1 10)))) (comment - + (nth (macroexpand (read-string "(def archive? (clojure.core/fn ([f] (file-ext? f .jar .zip))))")) 2) + (hunt-down-source 'orchard.util.os-test/cache-dir-windows-test) (fn-deps #'user/jdk8?) - (fn-deps #'orchard.meta/ns-file) + (fn-deps #'orchard.util.os-test/cache-dir-windows-test) + (fn-deps #'orchard.xref/fn->sym) (def vars (q/vars {:ns-query {:project? true} :private? true})) - (map fn-deps vars) - (drop 100 vars) - (when-let [x (fn? (as-val user/jdk8?))] - "some")) \ No newline at end of file + (map fn-deps vars)) \ No newline at end of file diff --git a/test/orchard/xref_test.clj b/test/orchard/xref_test.clj index 3f57368a..03799d04 100644 --- a/test/orchard/xref_test.clj +++ b/test/orchard/xref_test.clj @@ -43,5 +43,4 @@ (is (= (xref/fn-refs 'orchard.xref-test/dummy-fn) '())) (is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn))) (testing "with a lambda" - (is (= (xref/fn-refs dummy-fn) '())) (is (contains? (into #{} (xref/fn-refs #'times)) #'orchard.xref-test/dummy-fn)))) From 8b09fea055ce93ce7a98ca5fb9ce8af605f008b4 Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Mon, 3 Jan 2022 20:11:44 +0100 Subject: [PATCH 03/18] fix reflection warning --- project.clj | 3 ++- src/orchard/xref.clj | 12 +++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/project.clj b/project.clj index 9d2f3c2d..c36104fe 100644 --- a/project.clj +++ b/project.clj @@ -84,4 +84,5 @@ System/getenv (doto assert) (.contains "enrich-classpath")))) - (conj 'orchard.java.legacy-parser))}}}) + (conj 'orchard.java.legacy-parser)) + :reflection {'orchard.xref true}}}}) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index 24b1844c..147954b8 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -3,6 +3,7 @@ references." {:added "0.5"} (:require + [clojure.string] [clojure.repl :as repl] [orchard.query :as q])) @@ -31,13 +32,13 @@ (symbol? thing) (var-get (find-var thing)) (fn? thing) thing)) -(defn- f->sym [f] +(defn- f->sym [^clojure.lang.AFn f] (-> f .getClass .getName repl/demunge symbol)) (defn- fn-source [f] (hunt-down-source (f->sym f))) -(defn- fn-name [f] +(defn- fn-name [^java.lang.Class f] (-> f .getName repl/demunge symbol)) (defonce classbytes (atom {})) @@ -75,9 +76,9 @@ (defn- fn-deps-class [val] - (let [v (if (class? val) - val - (eval val))] + (let [^java.lang.Class v (if (class? val) + val + (eval val))] (set (some->> v .getDeclaredFields (keep (fn [^java.lang.reflect.Field f] (or (and (identical? clojure.lang.Var (.getType f)) @@ -133,5 +134,6 @@ (fn-deps #'user/jdk8?) (fn-deps #'orchard.util.os-test/cache-dir-windows-test) (fn-deps #'orchard.xref/fn->sym) + (supers (type @clojure.lang.Compiler/LOADER)) (def vars (q/vars {:ns-query {:project? true} :private? true})) (map fn-deps vars)) \ No newline at end of file From eccacaeb2ab619c74f80a4f008abe7e7c2a8461f Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Tue, 4 Jan 2022 14:18:19 +0100 Subject: [PATCH 04/18] fix kondo and eastwood errors --- README.md | 6 ++++++ project.clj | 2 +- src/orchard/xref.clj | 28 ++++++++++++---------------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 7edd6e8b..18c2c3f5 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,12 @@ So far, Orchard follows these options, which can be specified as Java system pro * `"-Dorchard.initialize-cache.silent=true"` (default: `true`) * if `false`, the _class info cache_ initialization may print warnings (possibly spurious ones). +## Tests and formatting + +To run the CI tasks locally use: + +`make test cljfmt eastwood kondo` + ## History Originally [SLIME][] was the most diff --git a/project.clj b/project.clj index c36104fe..40c51fd8 100644 --- a/project.clj +++ b/project.clj @@ -85,4 +85,4 @@ (doto assert) (.contains "enrich-classpath")))) (conj 'orchard.java.legacy-parser)) - :reflection {'orchard.xref true}}}}) + :ignored-faults {:reflection {orchard.xref [{:line 53}]}}}}}) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index 147954b8..3882fb2e 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -3,15 +3,15 @@ references." {:added "0.5"} (:require - [clojure.string] [clojure.repl :as repl] + [clojure.string] [orchard.query :as q])) (defn- hunt-down-source [fn-sym] - (let [{:keys [source file line column]} (-> fn-sym - resolve - meta)] + (let [{:keys [source file line]} (-> fn-sym + resolve + meta)] (try (or source (and file (read-string {:read-cond :allow} (or @@ -22,7 +22,7 @@ (drop (dec line)) (clojure.string/join "\n")) "nil")))) - (catch Exception ex)))) ;; explodes on namespaced keywords + (catch Exception _)))) ;; explodes on namespaced keywords (defn- as-val "Convert `thing` to a function value." @@ -75,10 +75,10 @@ (pop-thread-bindings)))) (defn- fn-deps-class - [val] - (let [^java.lang.Class v (if (class? val) - val - (eval val))] + [v] + (let [^java.lang.Class v (if (class? v) + v + (eval v))] (set (some->> v .getDeclaredFields (keep (fn [^java.lang.reflect.Field f] (or (and (identical? clojure.lang.Var (.getType f)) @@ -119,20 +119,16 @@ "Find all functions that refer `var`. `var` can be a function value, a var or a symbol." {:added "0.5"} - [var] - (let [var (as-var var) + [v] + (let [var (as-var v) all-vars (q/vars {:ns-query {:project? true} :private? true}) deps-map (zipmap all-vars (map fn-deps all-vars))] (map first (filter (fn [[_k v]] (contains? v var)) deps-map)))) -(defn- dummy-fn [_x] - (map #(* % 2) (filter even? (range 1 10)))) - (comment (nth (macroexpand (read-string "(def archive? (clojure.core/fn ([f] (file-ext? f .jar .zip))))")) 2) (hunt-down-source 'orchard.util.os-test/cache-dir-windows-test) - (fn-deps #'user/jdk8?) - (fn-deps #'orchard.util.os-test/cache-dir-windows-test) + (fn-deps #'fn-refs) (fn-deps #'orchard.xref/fn->sym) (supers (type @clojure.lang.Compiler/LOADER)) (def vars (q/vars {:ns-query {:project? true} :private? true})) From 76bc5d561cdb11441a3a4c7ae2049b460ee5073c Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Wed, 5 Jan 2022 00:41:22 +0100 Subject: [PATCH 05/18] added clojure classLoader cache solution --- src/orchard/xref.clj | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index 3882fb2e..22fb0068 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -4,7 +4,7 @@ {:added "0.5"} (:require [clojure.repl :as repl] - [clojure.string] + [clojure.string :as str] [orchard.query :as q])) (defn- hunt-down-source @@ -88,7 +88,7 @@ (.get f (fn-name v))) nil))))))) -(defn fn-deps +(defn fn-deps-compiling "Returns a set with all the functions invoked by `val`. `val` can be a function value, a var or a symbol." {:added "0.5"} @@ -102,6 +102,17 @@ deps (set (mapcat #(-> % symbol fn-deps-class) class-names))] deps)))) +(defn fn-deps [s] + (when-let [^clojure.lang.AFn v (as-val s)] + (let [f-class-name (-> v .getClass .getName) + ^java.lang.reflect.Field field (->> clojure.lang.DynamicClassLoader .getDeclaredFields second) + classes (into {} (.get field clojure.lang.DynamicClassLoader)) + filtered-classes (->> classes + (filter (fn [[k _v]] (clojure.string/includes? k f-class-name))) + (map (fn [[_k v]] (.get ^java.lang.ref.Reference v)))) + deps (set (mapcat fn-deps-class filtered-classes))] + deps))) + (defn- fn->sym "Convert a function value `f` to symbol." [f] @@ -130,6 +141,7 @@ (hunt-down-source 'orchard.util.os-test/cache-dir-windows-test) (fn-deps #'fn-refs) (fn-deps #'orchard.xref/fn->sym) + (fn-refs #'orchard.xref/fn->sym) (supers (type @clojure.lang.Compiler/LOADER)) (def vars (q/vars {:ns-query {:project? true} :private? true})) (map fn-deps vars)) \ No newline at end of file From 2183ebec9bcefe7183f1c2952790d8be9d4cfda0 Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Wed, 5 Jan 2022 00:53:37 +0100 Subject: [PATCH 06/18] illegal access fix --- project.clj | 2 +- src/orchard/xref.clj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/project.clj b/project.clj index 40c51fd8..23fa8613 100644 --- a/project.clj +++ b/project.clj @@ -85,4 +85,4 @@ (doto assert) (.contains "enrich-classpath")))) (conj 'orchard.java.legacy-parser)) - :ignored-faults {:reflection {orchard.xref [{:line 53}]}}}}}) + :ignored-faults {:reflection {orchard.xref [{:line 53} {:line 109}]}}}}}) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index 22fb0068..e3a9bf8a 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -105,7 +105,7 @@ (defn fn-deps [s] (when-let [^clojure.lang.AFn v (as-val s)] (let [f-class-name (-> v .getClass .getName) - ^java.lang.reflect.Field field (->> clojure.lang.DynamicClassLoader .getDeclaredFields second) + field (->> clojure.lang.DynamicClassLoader .getDeclaredFields second) classes (into {} (.get field clojure.lang.DynamicClassLoader)) filtered-classes (->> classes (filter (fn [[k _v]] (clojure.string/includes? k f-class-name))) From 83e642d3e3a801fa54f3d70e41d840aff636428e Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Wed, 5 Jan 2022 03:22:49 +0100 Subject: [PATCH 07/18] removed recompile version --- project.clj | 2 +- src/orchard/xref.clj | 79 ++++---------------------------------------- 2 files changed, 8 insertions(+), 73 deletions(-) diff --git a/project.clj b/project.clj index 23fa8613..44866045 100644 --- a/project.clj +++ b/project.clj @@ -85,4 +85,4 @@ (doto assert) (.contains "enrich-classpath")))) (conj 'orchard.java.legacy-parser)) - :ignored-faults {:reflection {orchard.xref [{:line 53} {:line 109}]}}}}}) + :ignored-faults {:reflection {orchard.xref [{:line 45}]}}}}}) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index e3a9bf8a..11f0ea3a 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -7,23 +7,6 @@ [clojure.string :as str] [orchard.query :as q])) -(defn- hunt-down-source - [fn-sym] - (let [{:keys [source file line]} (-> fn-sym - resolve - meta)] - (try (or source - (and file (read-string {:read-cond :allow} - (or - (clojure.repl/source-fn fn-sym) - (->> file - slurp - clojure.string/split-lines - (drop (dec line)) - (clojure.string/join "\n")) - "nil")))) - (catch Exception _)))) ;; explodes on namespaced keywords - (defn- as-val "Convert `thing` to a function value." [thing] @@ -32,48 +15,9 @@ (symbol? thing) (var-get (find-var thing)) (fn? thing) thing)) -(defn- f->sym [^clojure.lang.AFn f] - (-> f .getClass .getName repl/demunge symbol)) - -(defn- fn-source [f] - (hunt-down-source (f->sym f))) - (defn- fn-name [^java.lang.Class f] (-> f .getName repl/demunge symbol)) -(defonce classbytes (atom {})) - -(defn- recompile [ns-sym form] - (push-thread-bindings - {clojure.lang.Compiler/LOADER - (proxy [clojure.lang.DynamicClassLoader] [@clojure.lang.Compiler/LOADER] - (defineClass - ([name bytes src] - (swap! classbytes assoc name bytes) - (proxy-super defineClass name bytes src))))}) - (try - (let [line @clojure.lang.Compiler/LINE - column @clojure.lang.Compiler/COLUMN - line (if-let [line (:line (meta form))] - line - line) - column (if-let [column (:column (meta form))] - column - column)] - (push-thread-bindings {clojure.lang.Compiler/LINE line - clojure.lang.Compiler/COLUMN column}) - (try - (let [form (macroexpand form)] - (when (and (coll? form) (= 'clojure.core/fn (first (nth form 2 nil)))) - (binding [*ns* (create-ns ns-sym)] - (clojure.lang.Compiler/analyze - clojure.lang.Compiler$C/EVAL - (nth form 2))))) - (finally - (pop-thread-bindings)))) - (finally - (pop-thread-bindings)))) - (defn- fn-deps-class [v] (let [^java.lang.Class v (if (class? v) @@ -88,25 +32,17 @@ (.get f (fn-name v))) nil))))))) -(defn fn-deps-compiling +(defn fn-deps "Returns a set with all the functions invoked by `val`. `val` can be a function value, a var or a symbol." {:added "0.5"} - [f] - (reset! classbytes {}) - (let [f (as-val f)] - (when-let [source (and (fn? f) (fn-source f))] - (let [sym (f->sym f) - b (do (recompile (-> sym namespace symbol) source) @classbytes) - class-names (map first b) - deps (set (mapcat #(-> % symbol fn-deps-class) class-names))] - deps)))) - -(defn fn-deps [s] + [s] (when-let [^clojure.lang.AFn v (as-val s)] (let [f-class-name (-> v .getClass .getName) - field (->> clojure.lang.DynamicClassLoader .getDeclaredFields second) - classes (into {} (.get field clojure.lang.DynamicClassLoader)) + ;; breaks when called with + ;; (.getDeclaredField clojure.lang.DynamicClassLoader "classCache") + classCache (->> clojure.lang.DynamicClassLoader .getDeclaredFields second) + classes (into {} (.get classCache clojure.lang.DynamicClassLoader)) filtered-classes (->> classes (filter (fn [[k _v]] (clojure.string/includes? k f-class-name))) (map (fn [[_k v]] (.get ^java.lang.ref.Reference v)))) @@ -138,10 +74,9 @@ (comment (nth (macroexpand (read-string "(def archive? (clojure.core/fn ([f] (file-ext? f .jar .zip))))")) 2) - (hunt-down-source 'orchard.util.os-test/cache-dir-windows-test) (fn-deps #'fn-refs) (fn-deps #'orchard.xref/fn->sym) (fn-refs #'orchard.xref/fn->sym) - (supers (type @clojure.lang.Compiler/LOADER)) + (into {} (.get (->> clojure.lang.DynamicClassLoader .getDeclaredFields second) clojure.lang.DynamicClassLoader)) (def vars (q/vars {:ns-query {:project? true} :private? true})) (map fn-deps vars)) \ No newline at end of file From b0eeabcf46a543f6aca64f0ac7d01bd277bfbba1 Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Wed, 5 Jan 2022 03:25:12 +0100 Subject: [PATCH 08/18] clfmt fix --- src/orchard/xref.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index 11f0ea3a..d20cdb23 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -32,7 +32,7 @@ (.get f (fn-name v))) nil))))))) -(defn fn-deps +(defn fn-deps "Returns a set with all the functions invoked by `val`. `val` can be a function value, a var or a symbol." {:added "0.5"} From bc53e140c0cd9daa14fd6cf1694648c8ec1f93ab Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Thu, 6 Jan 2022 17:40:58 +0100 Subject: [PATCH 09/18] cleanup + performance --- project.clj | 3 +-- src/orchard/xref.clj | 48 +++++++++++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/project.clj b/project.clj index 44866045..9d2f3c2d 100644 --- a/project.clj +++ b/project.clj @@ -84,5 +84,4 @@ System/getenv (doto assert) (.contains "enrich-classpath")))) - (conj 'orchard.java.legacy-parser)) - :ignored-faults {:reflection {orchard.xref [{:line 45}]}}}}}) + (conj 'orchard.java.legacy-parser))}}}) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index d20cdb23..d94ce373 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -18,7 +18,10 @@ (defn- fn-name [^java.lang.Class f] (-> f .getName repl/demunge symbol)) -(defn- fn-deps-class +(defn fn-deps-class + "Returns a set with all the functions invoked by `v`. + `v` can be a function class or a symbol." + {:added "0.8"} [v] (let [^java.lang.Class v (if (class? v) v @@ -32,22 +35,26 @@ (.get f (fn-name v))) nil))))))) +(def ^java.lang.reflect.Field classCache + (let [classCache* (.getDeclaredField clojure.lang.DynamicClassLoader "classCache")] + (.setAccessible classCache* true) + (.get classCache* clojure.lang.DynamicClassLoader))) + (defn fn-deps - "Returns a set with all the functions invoked by `val`. - `val` can be a function value, a var or a symbol." + "Returns a set with all the functions invoked by `v` and its lambdas. + `v` can be a function value, a var or a symbol. + If a function was defined multiple times, old lambda deps will + be returned." {:added "0.5"} - [s] - (when-let [^clojure.lang.AFn v (as-val s)] - (let [f-class-name (-> v .getClass .getName) - ;; breaks when called with - ;; (.getDeclaredField clojure.lang.DynamicClassLoader "classCache") - classCache (->> clojure.lang.DynamicClassLoader .getDeclaredFields second) - classes (into {} (.get classCache clojure.lang.DynamicClassLoader)) - filtered-classes (->> classes - (filter (fn [[k _v]] (clojure.string/includes? k f-class-name))) - (map (fn [[_k v]] (.get ^java.lang.ref.Reference v)))) - deps (set (mapcat fn-deps-class filtered-classes))] - deps))) + [v] + (when-let [^clojure.lang.AFn v (as-val v)] + (let [f-class-name (-> v .getClass .getName)] + ;; this uses the implementation detail that the clojure compiler always + ;; prefixes names of lambdas with the name of its surrounding function class + (into #{} (comp (filter (fn [[k _v]] (clojure.string/includes? k f-class-name))) + (map (fn [[_k v]] (.get ^java.lang.ref.Reference v))) + (mapcat fn-deps-class)) + classCache)))) (defn- fn->sym "Convert a function value `f` to symbol." @@ -73,10 +80,15 @@ (map first (filter (fn [[_k v]] (contains? v var)) deps-map)))) (comment - (nth (macroexpand (read-string "(def archive? (clojure.core/fn ([f] (file-ext? f .jar .zip))))")) 2) (fn-deps #'fn-refs) - (fn-deps #'orchard.xref/fn->sym) + (fn-deps #'orchard.xref/fn-deps) (fn-refs #'orchard.xref/fn->sym) - (into {} (.get (->> clojure.lang.DynamicClassLoader .getDeclaredFields second) clojure.lang.DynamicClassLoader)) + + (let [f-class-name (-> orchard.xref/fn-deps .getClass .getName) + classes (into #{} (comp (filter (fn [[k _v]] (clojure.string/includes? k f-class-name))) + (map (fn [[_k v]] (.get ^java.lang.ref.Reference v)))) + classCache)] + classes) (def vars (q/vars {:ns-query {:project? true} :private? true})) + (map fn-deps vars)) \ No newline at end of file From 269e6738fc476998b5f185bc7ea298c9dc75e697 Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Fri, 7 Jan 2022 01:06:13 +0100 Subject: [PATCH 10/18] added softreference clear attempts --- src/orchard/xref.clj | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index d94ce373..c46d74db 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -35,7 +35,7 @@ (.get f (fn-name v))) nil))))))) -(def ^java.lang.reflect.Field classCache +(def classCache (let [classCache* (.getDeclaredField clojure.lang.DynamicClassLoader "classCache")] (.setAccessible classCache* true) (.get classCache* clojure.lang.DynamicClassLoader))) @@ -44,7 +44,9 @@ "Returns a set with all the functions invoked by `v` and its lambdas. `v` can be a function value, a var or a symbol. If a function was defined multiple times, old lambda deps will - be returned." + be returned. + This does not return functions marked with meta :inline like + + since they are already compiled away at this point." {:added "0.5"} [v] (when-let [^clojure.lang.AFn v (as-val v)] @@ -80,6 +82,17 @@ (map first (filter (fn [[_k v]] (contains? v var)) deps-map)))) (comment + (defn oom [] + (try (let [memKiller (java.util.ArrayList.)] + (loop [free 10000000] + (.add memKiller (object-array free)) + (.get memKiller 0) + (recur 100000 #_(if (< (Math/abs (.. Runtime (getRuntime) (freeMemory))) Integer/MAX_VALUE) + (Math/abs (.. Runtime (getRuntime) (freeMemory))) + Integer/MAX_VALUE)))) + (catch OutOfMemoryError _ + (println "freed")))) + (fn-deps #'fn-refs) (fn-deps #'orchard.xref/fn-deps) (fn-refs #'orchard.xref/fn->sym) @@ -89,6 +102,13 @@ (map (fn [[_k v]] (.get ^java.lang.ref.Reference v)))) classCache)] classes) + + (let [memKiller (java.util.ArrayList.)] + (loop [free (.. Runtime (getRuntime) (freeMemory))] + (.add memKiller (object-array free)) + (recur (.. Runtime (getRuntime) (freeMemory))))) + (oom) + (Math/min 1 2) (def vars (q/vars {:ns-query {:project? true} :private? true})) (map fn-deps vars)) \ No newline at end of file From 0d28c4f895b6d84acacc763475b57c2ddd16d623 Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Fri, 7 Jan 2022 15:59:31 +0100 Subject: [PATCH 11/18] added documentation --- README.md | 14 +++++++++++++- src/orchard/xref.clj | 10 ++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 18c2c3f5..dd211743 100644 --- a/README.md +++ b/README.md @@ -84,12 +84,24 @@ Just add `orchard` as a dependency and start hacking. Consult the [API documentation](https://cljdoc.org/d/cider/orchard/CURRENT) to get a better idea about the functionality that's provided. -#### Using `enrich-classpath` for best results +### Using `enrich-classpath` for best results There are features that Orchard intends to provide (especially, those related to Java interaction) which need to assume a pre-existing initial classpath that already has various desirable items, such as the JDK sources, third-party sources, special jars such as `tools` (for JDK8), a given project's own Java sources... all that is a domain in itself, which is why our [enrich-classpath](https://github.com/clojure-emacs/enrich-classpath) project does it. For getting the most out of Orchard, it is therefore recommended/necessary to use `enrich-classpath`. Please refer to its installation/usage instructions. +### xref/fn-deps and xref/fn-refs limitations + +These functions use a Clojure compiler implementation detail to find references to other function var dependencies. + +You can find a more in-depth explanation in this [post](https://lukas-domagala.de/blog/clojure-analysis-and-introspection.html). + +The important implications from this are: + +* very fast +* functions marked with meta :inline will not be found (inc, +, ...) +* redefining function vars that include lambdas will still return the dependencies of the old plus the new ones + ## Configuration options So far, Orchard follows these options, which can be specified as Java system properties diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index c46d74db..a0329200 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -35,7 +35,13 @@ (.get f (fn-name v))) nil))))))) -(def classCache +(def ^:private classCache + "Reference to Clojures class cache. + This holds of classes compiled by the Clojure compiler, + one class per function and one per repl eval. + This field is package private, so it has to be set to + accessible otherwise an IllegalAccess exception would + be thrown." (let [classCache* (.getDeclaredField clojure.lang.DynamicClassLoader "classCache")] (.setAccessible classCache* true) (.get classCache* clojure.lang.DynamicClassLoader))) @@ -97,7 +103,7 @@ (fn-deps #'orchard.xref/fn-deps) (fn-refs #'orchard.xref/fn->sym) - (let [f-class-name (-> orchard.xref/fn-deps .getClass .getName) + (let [f-class-name "orchard.xref" #_(-> orchard.xref/fn-deps .getClass .getName) classes (into #{} (comp (filter (fn [[k _v]] (clojure.string/includes? k f-class-name))) (map (fn [[_k v]] (.get ^java.lang.ref.Reference v)))) classCache)] From 63c84638dd21de0ce2a132ca169268babcd60897 Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Fri, 7 Jan 2022 16:01:46 +0100 Subject: [PATCH 12/18] fix classCache casing --- src/orchard/xref.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index a0329200..b2ece81c 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -35,7 +35,7 @@ (.get f (fn-name v))) nil))))))) -(def ^:private classCache +(def ^:private class-cache "Reference to Clojures class cache. This holds of classes compiled by the Clojure compiler, one class per function and one per repl eval. @@ -62,7 +62,7 @@ (into #{} (comp (filter (fn [[k _v]] (clojure.string/includes? k f-class-name))) (map (fn [[_k v]] (.get ^java.lang.ref.Reference v))) (mapcat fn-deps-class)) - classCache)))) + class-cache)))) (defn- fn->sym "Convert a function value `f` to symbol." @@ -106,7 +106,7 @@ (let [f-class-name "orchard.xref" #_(-> orchard.xref/fn-deps .getClass .getName) classes (into #{} (comp (filter (fn [[k _v]] (clojure.string/includes? k f-class-name))) (map (fn [[_k v]] (.get ^java.lang.ref.Reference v)))) - classCache)] + class-cache)] classes) (let [memKiller (java.util.ArrayList.)] From a67fb75c611f7886bcb06167734b692e26a4be26 Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Fri, 7 Jan 2022 18:40:54 +0100 Subject: [PATCH 13/18] changelog --- CHANGELOG.md | 6 ++++++ README.md | 1 + 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b2b543b..95288d6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## master (unreleased) +## 0.9.0 (2022-01-8) + +* [#51](https://github.com/clojure-emacs/orchard/issues/51): extend find-usages + * `orchard.xref/fn-deps` now also finds anonymous function dependencies + * added `orchard.xref/fn-deps-class` as a lower level API so you can still get the main functions deps only + ## 0.8.0 (2021-12-15) ### Changes diff --git a/README.md b/README.md index dd211743..e6e688b4 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ The important implications from this are: * very fast * functions marked with meta :inline will not be found (inc, +, ...) * redefining function vars that include lambdas will still return the dependencies of the old plus the new ones +([explanation](https://lukas-domagala.de/blog/clojure-compiler-class-cache.html)) ## Configuration options From d5c8f6dc846f0a932ada213c0af852edccc5fb22 Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Sat, 8 Jan 2022 02:46:33 +0100 Subject: [PATCH 14/18] review fixes --- README.md | 2 +- src/orchard/xref.clj | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e6e688b4..cff169d3 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ So far, Orchard follows these options, which can be specified as Java system pro To run the CI tasks locally use: -`make test cljfmt eastwood kondo` +`make test cljfmt kondo eastwood` ## History diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index b2ece81c..2ea647c8 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -7,7 +7,7 @@ [clojure.string :as str] [orchard.query :as q])) -(defn- as-val +(defn- to-fn "Convert `thing` to a function value." [thing] (cond @@ -26,14 +26,14 @@ (let [^java.lang.Class v (if (class? v) v (eval v))] - (set (some->> v .getDeclaredFields - (keep (fn [^java.lang.reflect.Field f] - (or (and (identical? clojure.lang.Var (.getType f)) - (java.lang.reflect.Modifier/isPublic (.getModifiers f)) - (java.lang.reflect.Modifier/isStatic (.getModifiers f)) - (-> f .getName (.startsWith "const__")) - (.get f (fn-name v))) - nil))))))) + (into #{} (keep (fn [^java.lang.reflect.Field f] + (or (and (identical? clojure.lang.Var (.getType f)) + (java.lang.reflect.Modifier/isPublic (.getModifiers f)) + (java.lang.reflect.Modifier/isStatic (.getModifiers f)) + (-> f .getName (.startsWith "const__")) + (.get f (fn-name v))) + nil)) + (.getDeclaredFields v))))) (def ^:private class-cache "Reference to Clojures class cache. @@ -47,20 +47,20 @@ (.get classCache* clojure.lang.DynamicClassLoader))) (defn fn-deps - "Returns a set with all the functions invoked by `v` and its lambdas. + "Returns a set with all the functions invoked inside `v` or any contained anonymous functions. `v` can be a function value, a var or a symbol. If a function was defined multiple times, old lambda deps will be returned. - This does not return functions marked with meta :inline like + + This does not return functions marked with meta :inline like `+` since they are already compiled away at this point." {:added "0.5"} [v] - (when-let [^clojure.lang.AFn v (as-val v)] + (when-let [^clojure.lang.AFn v (to-fn v)] (let [f-class-name (-> v .getClass .getName)] ;; this uses the implementation detail that the clojure compiler always ;; prefixes names of lambdas with the name of its surrounding function class (into #{} (comp (filter (fn [[k _v]] (clojure.string/includes? k f-class-name))) - (map (fn [[_k v]] (.get ^java.lang.ref.Reference v))) + (map (fn [[_k value]] (.get ^java.lang.ref.Reference value))) (mapcat fn-deps-class)) class-cache)))) From 2689a66eed79751db44c358465e139c4bb30bdbb Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Sat, 8 Jan 2022 21:05:36 +0100 Subject: [PATCH 15/18] added fn-transitive deps --- CHANGELOG.md | 3 +++ README.md | 1 + src/orchard/xref.clj | 29 ++++++++++++++++++++++------- test/orchard/xref_test.clj | 11 ++++++++++- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95288d6e..f093facc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,12 @@ ## 0.9.0 (2022-01-8) +### Changes + * [#51](https://github.com/clojure-emacs/orchard/issues/51): extend find-usages * `orchard.xref/fn-deps` now also finds anonymous function dependencies * added `orchard.xref/fn-deps-class` as a lower level API so you can still get the main functions deps only + * added `orchard.xref/fn-transitive-deps` ## 0.8.0 (2021-12-15) diff --git a/README.md b/README.md index cff169d3..c90438a0 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ The important implications from this are: * functions marked with meta :inline will not be found (inc, +, ...) * redefining function vars that include lambdas will still return the dependencies of the old plus the new ones ([explanation](https://lukas-domagala.de/blog/clojure-compiler-class-cache.html)) +* does not work on AoT compiled functions ## Configuration options diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index 2ea647c8..95627afd 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -4,6 +4,7 @@ {:added "0.5"} (:require [clojure.repl :as repl] + [clojure.set :as set] [clojure.string :as str] [orchard.query :as q])) @@ -21,7 +22,7 @@ (defn fn-deps-class "Returns a set with all the functions invoked by `v`. `v` can be a function class or a symbol." - {:added "0.8"} + {:added "0.9"} [v] (let [^java.lang.Class v (if (class? v) v @@ -64,6 +65,23 @@ (mapcat fn-deps-class)) class-cache)))) +(defn fn-transitive-deps + "Returns a set with all the functions invoked inside `v` or inside those functions. + `v` can be a function value, a var or a symbol." + {:added "0.9"} + [v] + (let [deps (fn-deps v)] + (loop [checked #{} + to-check deps + deps deps] + (cond + (empty? to-check) deps + :else (let [[current & remaining] to-check + new-deps (fn-deps current)] + (recur (conj checked current) + (concat remaining (filter #(contains? deps %) new-deps)) + (set/union deps new-deps))))))) + (defn- fn->sym "Convert a function value `f` to symbol." [f] @@ -88,6 +106,7 @@ (map first (filter (fn [[_k v]] (contains? v var)) deps-map)))) (comment + ;; this can be used to blow up memory, which will clear the class cache of old references (defn oom [] (try (let [memKiller (java.util.ArrayList.)] (loop [free 10000000] @@ -103,18 +122,14 @@ (fn-deps #'orchard.xref/fn-deps) (fn-refs #'orchard.xref/fn->sym) + ;; returns all classes in this ns, even repl eval'd (let [f-class-name "orchard.xref" #_(-> orchard.xref/fn-deps .getClass .getName) classes (into #{} (comp (filter (fn [[k _v]] (clojure.string/includes? k f-class-name))) (map (fn [[_k v]] (.get ^java.lang.ref.Reference v)))) class-cache)] classes) - (let [memKiller (java.util.ArrayList.)] - (loop [free (.. Runtime (getRuntime) (freeMemory))] - (.add memKiller (object-array free)) - (recur (.. Runtime (getRuntime) (freeMemory))))) (oom) - (Math/min 1 2) (def vars (q/vars {:ns-query {:project? true} :private? true})) - (map fn-deps vars)) \ No newline at end of file + (map fn-deps vars)) diff --git a/test/orchard/xref_test.clj b/test/orchard/xref_test.clj index 03799d04..ea315e14 100644 --- a/test/orchard/xref_test.clj +++ b/test/orchard/xref_test.clj @@ -3,9 +3,12 @@ [clojure.test :refer [deftest is testing]] [orchard.xref :as xref])) -(defn- times [a b] +(defn- times* [a b] (* a b)) +(defn- times [a b] + (times* a b)) + (defn- dummy-fn [_x] (map #(times % 2) (filter even? (range 1 10)))) @@ -44,3 +47,9 @@ (is (contains? (into #{} (xref/fn-refs #'map)) #'orchard.xref-test/dummy-fn))) (testing "with a lambda" (is (contains? (into #{} (xref/fn-refs #'times)) #'orchard.xref-test/dummy-fn)))) + +(deftest fn-transitive-deps-test + (testing "basics" + (is (= (xref/fn-transitive-deps dummy-fn) + #{#'clojure.core/even? #'clojure.core/filter #'clojure.core/map + #'clojure.core/range #'orchard.xref-test/times #'orchard.xref-test/times*})))) From f0872f4f4113194a18e69908310df47fbf9cabb0 Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Sat, 8 Jan 2022 21:11:08 +0100 Subject: [PATCH 16/18] fix clojure 1.8 set access --- src/orchard/xref.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/orchard/xref.clj b/src/orchard/xref.clj index 95627afd..84416430 100644 --- a/src/orchard/xref.clj +++ b/src/orchard/xref.clj @@ -72,7 +72,7 @@ [v] (let [deps (fn-deps v)] (loop [checked #{} - to-check deps + to-check (into [] deps) deps deps] (cond (empty? to-check) deps From d30546b2761d277d37cd641fd2640fa1aa5de92c Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Sat, 8 Jan 2022 21:12:54 +0100 Subject: [PATCH 17/18] Update test/orchard/xref_test.clj Co-authored-by: vemv --- test/orchard/xref_test.clj | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/orchard/xref_test.clj b/test/orchard/xref_test.clj index ea315e14..119e68f7 100644 --- a/test/orchard/xref_test.clj +++ b/test/orchard/xref_test.clj @@ -50,6 +50,9 @@ (deftest fn-transitive-deps-test (testing "basics" - (is (= (xref/fn-transitive-deps dummy-fn) - #{#'clojure.core/even? #'clojure.core/filter #'clojure.core/map - #'clojure.core/range #'orchard.xref-test/times #'orchard.xref-test/times*})))) + (let [expected #{#'clojure.core/even? #'clojure.core/filter #'clojure.core/map + #'clojure.core/range #'orchard.xref-test/times #'orchard.xref-test/times*}] + (is (contains? expected #'orchard.xref-test/times*) + "Specifically includes `#'times*`, which is a transitive dep of `#'dummy-fn` (via `#'times`)") + (is (= expected + (xref/fn-transitive-deps dummy-fn)))))) From f44e878116e7a100d965adcc5e6f0c857f6d309a Mon Sep 17 00:00:00 2001 From: Lukas Domagala Date: Sat, 8 Jan 2022 21:46:43 +0100 Subject: [PATCH 18/18] cleaned up test --- test/orchard/xref_test.clj | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/test/orchard/xref_test.clj b/test/orchard/xref_test.clj index 119e68f7..56044ecf 100644 --- a/test/orchard/xref_test.clj +++ b/test/orchard/xref_test.clj @@ -3,28 +3,28 @@ [clojure.test :refer [deftest is testing]] [orchard.xref :as xref])) -(defn- times* [a b] +(defn- fn-transitive-dep [a b] (* a b)) -(defn- times [a b] - (times* a b)) +(defn- fn-dep [a b] + (fn-transitive-dep a b)) (defn- dummy-fn [_x] - (map #(times % 2) (filter even? (range 1 10)))) + (map #(fn-dep % 2) (filter even? (range 1 10)))) (deftest fn-deps-test (testing "with a fn value" (is (= (xref/fn-deps dummy-fn) #{#'clojure.core/map #'clojure.core/filter - #'clojure.core/even? #'clojure.core/range #'orchard.xref-test/times}))) + #'clojure.core/even? #'clojure.core/range #'orchard.xref-test/fn-dep}))) (testing "with a var" (is (= (xref/fn-deps #'dummy-fn) #{#'clojure.core/map #'clojure.core/filter - #'clojure.core/even? #'clojure.core/range #'orchard.xref-test/times}))) + #'clojure.core/even? #'clojure.core/range #'orchard.xref-test/fn-dep}))) (testing "with a symbol" (is (= (xref/fn-deps 'orchard.xref-test/dummy-fn) #{#'clojure.core/map #'clojure.core/filter - #'clojure.core/even? #'clojure.core/range #'orchard.xref-test/times})))) + #'clojure.core/even? #'clojure.core/range #'orchard.xref-test/fn-dep})))) ;; The mere presence of this var can reproduce a certain issue. See: ;; https://github.com/clojure-emacs/orchard/issues/135#issuecomment-939731698 @@ -45,14 +45,15 @@ (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 "with a lambda" - (is (contains? (into #{} (xref/fn-refs #'times)) #'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 #{#'clojure.core/even? #'clojure.core/filter #'clojure.core/map - #'clojure.core/range #'orchard.xref-test/times #'orchard.xref-test/times*}] - (is (contains? expected #'orchard.xref-test/times*) - "Specifically includes `#'times*`, which is a transitive dep of `#'dummy-fn` (via `#'times`)") + (let [expected #{#'orchard.xref-test/fn-deps-test #'orchard.xref-test/fn-dep #'clojure.core/even? + #'clojure.core/filter #'orchard.xref-test/fn-transitive-dep #'clojure.core/map + #'clojure.test/test-var #'clojure.core/range}] + (is (contains? expected #'orchard.xref-test/fn-transitive-dep) + "Specifically includes `#'fn-transitive-dep`, which is a transitive dep of `#'dummy-fn` (via `#'fn-dep`)") (is (= expected (xref/fn-transitive-deps dummy-fn))))))