From 453624789f8410f14678f7c8184811294e6d6c9b Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Fri, 21 Oct 2022 14:39:20 +0200 Subject: [PATCH] Fix #1845: add :derived-location to analysis (#1851) --- src/clj_kondo/hooks_api.clj | 4 +- src/clj_kondo/impl/analysis.clj | 22 +++++--- src/clj_kondo/impl/analyzer.clj | 9 ++-- src/clj_kondo/impl/linters.clj | 4 +- src/clj_kondo/impl/namespace.clj | 3 +- test/clj_kondo/analysis_test.clj | 92 +++++++++++++++++++++++++++++++- test/clj_kondo/test_utils.clj | 19 ++++--- 7 files changed, 129 insertions(+), 24 deletions(-) diff --git a/src/clj_kondo/hooks_api.clj b/src/clj_kondo/hooks_api.clj index 80a0745ae7..9a3ed19b91 100644 --- a/src/clj_kondo/hooks_api.clj +++ b/src/clj_kondo/hooks_api.clj @@ -122,14 +122,14 @@ (defn annotate {:no-doc true} [node original-meta] - (let [!!last-meta (volatile! original-meta)] + (let [!!last-meta (volatile! (assoc original-meta :derived-location true))] (prewalk (fn [node] (cond (and (instance? clj_kondo.impl.rewrite_clj.node.seq.SeqNode node) (identical? :list (utils/tag node))) (if-let [m (meta node)] (if-let [m (not-empty (select-keys m [:row :end-row :col :end-col]))] - (do (vreset! !!last-meta m) + (do (vreset! !!last-meta (assoc m :derived-location true)) (mark-generate node)) (-> (with-meta node (merge @!!last-meta (meta node))) diff --git a/src/clj_kondo/impl/analysis.clj b/src/clj_kondo/impl/analysis.clj index 69d07c7650..8239d638cb 100644 --- a/src/clj_kondo/impl/analysis.clj +++ b/src/clj_kondo/impl/analysis.clj @@ -39,7 +39,9 @@ :name-end-row :name-end-col :end-row - :end-col])) + :end-col + :derived-location + :derived-name-location])) :arity arity :lang lang :from-var in-def @@ -49,7 +51,7 @@ filename row col ns nom attrs] (when analysis (let [raw-attrs attrs - attrs (select-keys attrs [:private :macro :fixed-arities :varargs-min-arity + attrs (select-some attrs [:private :macro :fixed-arities :varargs-min-arity :doc :added :deprecated :test :export :defined-by :protocol-ns :protocol-name :imported-ns @@ -113,7 +115,7 @@ (when (and analysis (not (:clj-kondo.impl/generated binding))) (swap! analysis update :locals conj - (assoc-some (select-keys binding [:name :str :id :row :col :end-row :end-col :scope-end-col :scope-end-row]) + (assoc-some (select-keys binding [:name :str :id :row :col :end-row :end-col :scope-end-col :scope-end-row :derived-location]) :filename filename :lang (when (= :cljc (:base-lang ctx)) (:lang ctx)))))) @@ -121,7 +123,7 @@ (when (and analysis (not (:clj-kondo.impl/generated binding))) (swap! analysis update :local-usages conj - (assoc-some (select-keys usage [:id :row :col :end-row :end-col :name-row :name-col :name-end-row :name-end-col]) + (assoc-some (select-keys usage [:id :row :col :end-row :end-col :name-row :name-col :name-end-row :name-end-col :derived-location]) :name (:name binding) :filename filename :lang (when (= :cljc (:base-lang ctx)) (:lang ctx)) @@ -134,7 +136,8 @@ (assoc-some (select-keys usage [:row :col :end-row :end-col :alias :ns :keys-destructuring :keys-destructuring-ns-modifier - :reg :auto-resolved :namespace-from-prefix]) + :reg :auto-resolved :namespace-from-prefix + :derived-location]) :name (name (:name usage)) :filename filename :lang (when (= :cljc (:base-lang ctx)) (:lang ctx)) @@ -162,14 +165,16 @@ :row (:row method-meta) :col (:col method-meta) :end-row (:end-row method-meta) - :end-col (:end-col method-meta)}))))) + :end-col (:end-col method-meta) + :derived-location (:derived-location method-meta)}))))) (defn reg-instance-invocation! [ctx method-name-node] (when (:analyze-instance-invocations? ctx) (when-let [analysis (:analysis ctx)] (let [method-meta (meta method-name-node) - k :instance-invocations] + k :instance-invocations + derived-location (:derived-location method-meta)] (when k (swap! analysis update k conj (cond-> @@ -180,4 +185,5 @@ :name-end-row (:end-row method-meta) :name-end-col (:end-col method-meta)} (= :cljc (:base-lang ctx)) - (assoc :lang (:lang ctx))))))))) + (assoc :lang (:lang ctx)) + derived-location (assoc :derived-location true)))))))) diff --git a/src/clj_kondo/impl/analyzer.clj b/src/clj_kondo/impl/analyzer.clj index 4332de9918..fe1fb8a5d8 100644 --- a/src/clj_kondo/impl/analyzer.clj +++ b/src/clj_kondo/impl/analyzer.clj @@ -1751,7 +1751,8 @@ ;; :arg-types (:arg-types ctx) :interop? interop? :resolved-core? resolved-core? - :in-def (:in-def ctx)}))) + :in-def (:in-def ctx) + :derived-location (:derived-location (meta expr))}))) (and arity arg-count) (let [{:keys [:fixed-arities :varargs-min-arity]} arity config (:config ctx) @@ -2022,7 +2023,8 @@ :interop? interop? :resolved-core? resolved-core? :idx (:idx ctx) - :len (:len ctx)})) + :len (:len ctx) + :derived-location (:derived-location expr-meta)})) ;;;; This registers the namespace as used, to prevent unused warnings (namespace/reg-used-namespace! ctx ns-name @@ -2325,7 +2327,8 @@ :resolved-core? resolved-core? :redundant-fn-wrapper-parent-loc fn-parent-loc :idx (:idx ctx) - :len (:len ctx)} + :len (:len ctx) + :derived-location (:derived-location expr-meta)} ret-tag (or (:ret m) (types/ret-tag-from-call ctx proto-call expr)) call (cond-> proto-call diff --git a/src/clj_kondo/impl/linters.clj b/src/clj_kondo/impl/linters.clj index bbf8a3dc66..2b14dbe336 100644 --- a/src/clj_kondo/impl/linters.clj +++ b/src/clj_kondo/impl/linters.clj @@ -319,7 +319,9 @@ :name-end-row name-end-row :name-end-col name-end-col :end-row end-row - :end-col end-col)))) + :end-col end-col + :derived-location (:derived-location call) + :derived-name-location (:derived-location name-meta))))) call-config (:config call) fn-sym (symbol (str resolved-ns) (str fn-name)) diff --git a/src/clj_kondo/impl/namespace.clj b/src/clj_kondo/impl/namespace.clj index c039ba5cff..393aec358d 100644 --- a/src/clj_kondo/impl/namespace.clj +++ b/src/clj_kondo/impl/namespace.clj @@ -112,7 +112,8 @@ :row expr-row :col expr-col :end-row expr-end-row - :end-col expr-end-col) + :end-col expr-end-col + :derived-location (:derived-location m)) path [base-lang lang ns-sym] temp? (:temp metadata) config (:config ctx)] diff --git a/test/clj_kondo/analysis_test.clj b/test/clj_kondo/analysis_test.clj index 380deab97c..773c4abb14 100644 --- a/test/clj_kondo/analysis_test.clj +++ b/test/clj_kondo/analysis_test.clj @@ -991,6 +991,96 @@ " {:node (with-meta new-node (meta node))" " :defined-by 'user/defflow}))")}}}})))) +(deftest hooks-derived-location-test + (testing "without custom with-meta" + (testing "generated def var-usage" + (let [{:keys [var-definitions var-usages]} + (analyze "(user/defflow foobar)" + {:config {:analysis {:keywords true} + :hooks {:__dangerously-allow-string-hooks__ true + :analyze-call + {'user/defflow + (str '(require '[clj-kondo.hooks-api :as api]) + '(fn [{:keys [:node]}] + (let [[test-name] (rest (:children node)) + new-node (api/list-node + [(api/token-node 'def) + test-name])] + {:node new-node})))}}}})] + (assert-submaps + '[{:ns user, + :name foobar + :derived-location :submap/missing}] + var-definitions) + (assert-submaps + '[{:name defflow} + {:name def + :derived-location true + :derived-name-location true + :row 1 :col 1 :end-row 1 :end-col 22 + :name-row 1 :name-col 1 :name-end-row 1 :name-end-col 22}] + var-usages))) + (testing "generated keyword and let var-usage" + (let [{:keys [var-definitions var-usages keywords]} + (analyze "(user/deflet my-k)" + {:config {:analysis {:keywords true} + :hooks {:__dangerously-allow-string-hooks__ true + :analyze-call + {'user/deflet + (str + '(require '[clj-kondo.hooks-api :as api]) + '(fn [{:keys [node]}] + (let [[my-k] (rest (:children node)) + new-node (api/list-node + [(api/token-node 'let) + (api/vector-node [(api/token-node '_) + (api/keyword-node (keyword (api/sexpr my-k)))])])] + {:node new-node})))}}}})] + (assert-submaps + '[{:name "my-k" + :row 1 :col 1 :end-row 1 :end-col 19 + :derived-location true + :derived-name-location :submap/missing}] + keywords) + (is (empty? var-definitions)) + (assert-submaps + '[{:name deflet} + {:name let + :derived-location true + :derived-name-location true + :row 1 :col 1 :end-row 1 :end-col 19 + :name-row 1 :name-col 1 :name-end-row 1 :name-end-col 19}] + var-usages)))) + (testing "with custom with-meta" + (let [{:keys [var-definitions var-usages]} + (analyze "(user/defflow foobar)" + {:config {:analysis {:keywords true} + :hooks {:__dangerously-allow-string-hooks__ true + :analyze-call + {'user/defflow + (str '(require '[clj-kondo.hooks-api :as api]) + '(fn [{:keys [:node]}] + (let [[test-name] (rest (:children node)) + new-node (api/list-node + [(api/token-node 'def) + test-name])] + {:node (with-meta new-node {:row 10 :col 11 :end-row 12 :end-col 13}) + })))}}}})] + (assert-submaps + '[{:ns user, + :name foobar, + :derived-location :submap/missing + :derived-name-location :submap/missing}] + var-definitions) + (assert-submaps + '[{:name defflow} + {:name def + :derived-name-location true + :row 10 :col 11 :end-row 12 :end-col 13 + :name-row 10 :name-col 11 :name-end-row 12 :name-end-col 13 + :derived-location :submap/missing}] + var-usages)))) + (deftest hooks-custom-missing-meta-test (assert-submaps '[{:row 1 @@ -1063,7 +1153,7 @@ {:name f6, :defined-by clojure.core/def :arglist-strs ["[n]"]}] - var-definitions)))) + var-definitions)))) (deftest analysis-is-valid-edn-test (testing "solution for GH-476, CLJS with string require" diff --git a/test/clj_kondo/test_utils.clj b/test/clj_kondo/test_utils.clj index c935bf2cbd..70688b7490 100644 --- a/test/clj_kondo/test_utils.clj +++ b/test/clj_kondo/test_utils.clj @@ -34,14 +34,17 @@ [m1 m2] (cond (and (map? m1) (map? m2)) - (every? (fn [[k v]] (and (contains? m2 k) - (if (or (identical? k :filename) - (identical? k :file)) - (if (regex? v) - (re-find v (normalize-filename (get m2 k))) - (= (normalize-filename v) - (normalize-filename (get m2 k)))) - (submap? v (get m2 k))))) + (every? (fn [[k v]] + (if (identical? v :submap/missing) + (not (contains? m2 k)) + (and (contains? m2 k) + (if (or (identical? k :filename) + (identical? k :file)) + (if (regex? v) + (re-find v (normalize-filename (get m2 k))) + (= (normalize-filename v) + (normalize-filename (get m2 k)))) + (submap? v (get m2 k)))))) m1) (regex? m1) (re-find m1 m2)