Skip to content

Commit a4a4b46

Browse files
Consider a namespace as part of a layer only when the namespace is in the source-paths.
1 parent 8d643dc commit a4a4b46

File tree

11 files changed

+140
-90
lines changed

11 files changed

+140
-90
lines changed

lein-clj-depend/src/leiningen/clj_depend.clj

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
(:require [clj-depend.main :as clj-depend.main]
44
[leiningen.core.main :as leiningen.core]))
55

6-
(defn- project->args
6+
(defn ^:private project->args
77
[{:keys [root]} args]
88
(concat (or args [])
99
["--project-root" root]))
1010

11-
(defn- run!
11+
(defn ^:private run!
1212
[project args]
1313
(let [result (apply clj-depend.main/run! (project->args project args))]
1414
(when-let [message (:message result)]

src/clj_depend/analyzer.clj

+9-7
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
[clj-depend.analyzers.layer :as analyzers.layer]
44
[clj-depend.analyzers.rule :as analyzers.rule]))
55

6-
(defn- violations
6+
(defn ^:private violations
77
[config dependencies-by-namespace namespace]
8-
(let [dependencies (get dependencies-by-namespace namespace)
9-
circular-dependency-violations (analyzers.circular-dependency/analyze namespace dependencies dependencies-by-namespace)
10-
layer-violations (analyzers.layer/analyze config namespace dependencies)
11-
rule-violations (analyzers.rule/analyze config namespace dependencies)]
8+
(let [circular-dependency-violations (analyzers.circular-dependency/analyze namespace dependencies-by-namespace)
9+
layer-violations (analyzers.layer/analyze config namespace dependencies-by-namespace)
10+
rule-violations (analyzers.rule/analyze config namespace dependencies-by-namespace)]
1211
(not-empty (concat circular-dependency-violations layer-violations rule-violations))))
1312

1413
(defn analyze
1514
"Analyze namespaces dependencies."
16-
[{:keys [config dependencies-by-namespace]}]
17-
(flatten (keep #(violations config dependencies-by-namespace %) (keys dependencies-by-namespace))))
15+
[{:keys [config namespaces-to-be-analyzed namespaces-and-dependencies]}]
16+
(let [dependencies-by-namespace (reduce-kv (fn [m k v] (assoc m k (:dependencies (first v))))
17+
{}
18+
(group-by :namespace namespaces-and-dependencies))]
19+
(flatten (keep #(violations config dependencies-by-namespace %) namespaces-to-be-analyzed))))
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
(ns clj-depend.analyzers.circular-dependency)
22

33
(defn analyze
4-
[namespace dependencies dependencies-by-namespace]
5-
(->> dependencies-by-namespace
6-
(filter (fn [[k _]] (contains? dependencies k)))
7-
(filter (fn [[_ v]] (contains? v namespace)))
8-
(map (fn [[k _]] {:namespace namespace :dependency-namespace k :message (str "Circular dependency between " \" namespace \" " and " \" k \")}))))
4+
[namespace dependencies-by-namespace]
5+
(let [current-namespace-dependencies (get dependencies-by-namespace namespace)]
6+
(->> dependencies-by-namespace
7+
(filter (fn [[k _]] (contains? current-namespace-dependencies k)))
8+
(filter (fn [[_ v]] (contains? v namespace)))
9+
(map (fn [[k _]] {:namespace namespace :dependency-namespace k :message (str "Circular dependency between " \" namespace \" " and " \" k \")})))))

src/clj_depend/analyzers/layer.clj

+28-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
(ns clj-depend.analyzers.layer)
1+
(ns clj-depend.analyzers.layer
2+
(:require [clojure.set :as set]))
23

34
(defn ^:private layer-cannot-access-dependency-layer?
45
[config layer dependency-layer]
@@ -19,28 +20,42 @@
1920
(or (dependency-layer-cannot-be-accessed-by-layer? config dependency-layer layer)
2021
(layer-cannot-access-dependency-layer? config layer dependency-layer))))
2122

23+
(defn ^:private namespace-in-source-paths?
24+
[namespace dependencies-by-namespace]
25+
(contains? (set (keys dependencies-by-namespace)) namespace))
26+
2227
(defn ^:private namespace-belongs-to-layer?
23-
[config namespace layer]
28+
[config namespace layer dependencies-by-namespace]
2429
(let [namespaces (get-in config [:layers layer :namespaces])
25-
defined-by (get-in config [:layers layer :defined-by])]
26-
(or (some #{namespace} namespaces)
27-
(when defined-by (re-find (re-pattern defined-by) (str namespace))))))
30+
defined-by (get-in config [:layers layer :defined-by])
31+
only-ns-in-source-paths (get-in config [:layers layer :only-ns-in-source-paths])]
32+
(and (or (not only-ns-in-source-paths)
33+
(and only-ns-in-source-paths (namespace-in-source-paths? namespace dependencies-by-namespace)))
34+
(or (some #{namespace} namespaces)
35+
(when defined-by (re-find (re-pattern defined-by) (str namespace)))))))
2836

2937
(defn ^:private layer-by-namespace
30-
[config namespace]
31-
(some #(when (namespace-belongs-to-layer? config namespace %) %) (keys (:layers config))))
38+
[config namespace dependencies-by-namespace]
39+
(some #(when (namespace-belongs-to-layer? config namespace % dependencies-by-namespace) %) (keys (:layers config))))
3240

33-
(defn ^:private layer-and-namespace [config namespace dependency-namespace]
34-
(when-let [layer (layer-by-namespace config namespace)]
41+
(defn ^:private layer-and-namespace [config namespace dependency-namespace dependencies-by-namespace]
42+
(when-let [layer (layer-by-namespace config namespace dependencies-by-namespace)]
3543
{:namespace namespace
3644
:layer layer
3745
:dependency-namespace dependency-namespace
38-
:dependency-layer (layer-by-namespace config dependency-namespace)}))
46+
:dependency-layer (layer-by-namespace config dependency-namespace dependencies-by-namespace)}))
47+
48+
(defn ^:private namespace-dependencies
49+
[{:keys [only-ns-in-source-paths]} namespace dependencies-by-namespace]
50+
(let [namespace-dependencies (get dependencies-by-namespace namespace)]
51+
(if only-ns-in-source-paths
52+
(set/intersection (set namespace-dependencies) (set (keys dependencies-by-namespace)))
53+
namespace-dependencies)))
3954

4055
(defn analyze
41-
[config namespace dependencies]
42-
(->> dependencies
43-
(map #(layer-and-namespace config namespace %))
56+
[config namespace dependencies-by-namespace]
57+
(->> (get dependencies-by-namespace namespace)
58+
(map #(layer-and-namespace config namespace % dependencies-by-namespace))
4459
(filter #(violate? config %))
4560
(map (fn [{:keys [namespace dependency-namespace layer dependency-layer] :as violation}]
4661
(assoc violation :message (str \" namespace \" " should not depend on " \" dependency-namespace \" " (layer " \" layer \" " on " \" dependency-layer \" ")"))))))

src/clj_depend/analyzers/rule.clj

+5-4
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
(defn analyze
4343
[{:keys [rules]}
4444
namespace
45-
dependencies]
46-
(->> (filter #(rule-applies-to-namespace? % namespace) rules)
47-
(keep #(violations-by-rule % namespace dependencies))
48-
flatten))
45+
dependencies-by-namespace]
46+
(let [current-namespace-dependencies (get dependencies-by-namespace namespace)]
47+
(->> (filter #(rule-applies-to-namespace? % namespace) rules)
48+
(keep #(violations-by-rule % namespace current-namespace-dependencies))
49+
flatten)))

src/clj_depend/internal_api.clj

+46-27
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,68 @@
44
[clj-depend.parser :as parser]
55
[clj-depend.snapshot :as snapshot]
66
[clojure.java.io :as io]
7-
[clojure.string :as string]))
7+
[clojure.string :as string]
8+
[clojure.tools.namespace.file :as file]
9+
[clojure.tools.namespace.find :as namespace.find]
10+
[clojure.tools.namespace.parse :as namespace.parse]))
811

9-
(defn- ->project-root
12+
(defn ^:private ->project-root
1013
[{:keys [project-root]} context]
1114
(assoc context :project-root project-root))
1215

13-
(defn- ->config
16+
(defn ^:private ->config
1417
[{:keys [config]}
1518
{:keys [project-root] :as context}]
1619
(assoc context :config (config/resolve-config! project-root config)))
1720

18-
(defn ^:private file-within-some-source-paths?
19-
[file source-paths]
20-
(some #(.startsWith (.toPath file) (.toPath %)) source-paths))
21+
(defn ^:private source-paths-or-project-root->files
22+
[{:keys [project-root] {:keys [source-paths]} :config}]
23+
(if (not-empty source-paths)
24+
(map #(io/file project-root %) source-paths)
25+
#{project-root}))
2126

22-
(defn ^:private files-within-source-paths
23-
[files source-paths]
24-
(filter #(file-within-some-source-paths? % source-paths) files))
27+
(defn ^:private analyze?
28+
[{:keys [file namespace]} files-to-be-analyzed namespaces-to-be-analyzed]
29+
(boolean (cond
30+
(and (not-empty files-to-be-analyzed) (not-empty namespaces-to-be-analyzed))
31+
(and (some #(.startsWith (.toPath file) (.toPath %)) files-to-be-analyzed)
32+
(contains? namespaces-to-be-analyzed namespace))
2533

26-
(defn- ->files
27-
[{:keys [files]}
28-
{:keys [project-root] {:keys [source-paths]} :config :as context}]
29-
(let [source-paths (map #(io/file project-root %) source-paths)]
30-
(cond
31-
(not-empty files) (assoc context :files (files-within-source-paths files source-paths))
32-
(not-empty source-paths) (assoc context :files source-paths)
33-
:else (assoc context :files #{project-root}))))
34+
(not-empty files-to-be-analyzed)
35+
(some #(.startsWith (.toPath file) (.toPath %)) files-to-be-analyzed)
3436

35-
(defn- ->namespaces
36-
[{:keys [namespaces]}
37-
{:keys [files] :as context}]
38-
(let [ns-deps (parser/parse-clojure-files! files namespaces)]
39-
(assoc context :dependencies-by-namespace (reduce-kv (fn [m k v] (assoc m k (:dependencies (first v))))
40-
{}
41-
(group-by :name ns-deps)))))
37+
(not-empty namespaces-to-be-analyzed)
38+
(contains? namespaces-to-be-analyzed namespace)
4239

43-
(defn- build-context
40+
:else
41+
true)))
42+
43+
(defn ^:private ->namespaces-and-dependencies
44+
[_options
45+
context]
46+
(let [files (source-paths-or-project-root->files context)
47+
clojure-files (mapcat #(namespace.find/find-sources-in-dir %) files)]
48+
(assoc context :namespaces-and-dependencies (keep (fn [file]
49+
(when-let [ns-decl (file/read-file-ns-decl file)]
50+
{:namespace (namespace.parse/name-from-ns-decl ns-decl)
51+
:dependencies (namespace.parse/deps-from-ns-decl ns-decl)
52+
:file file})) clojure-files))))
53+
54+
(defn ^:private ->namespaces-to-be-analyzed
55+
[{files-to-be-analyzed :files
56+
namespaces-to-be-analyzed :namespaces}
57+
{:keys [namespaces-and-dependencies] :as context}]
58+
(assoc context :namespaces-to-be-analyzed (->> namespaces-and-dependencies
59+
(filter #(analyze? % files-to-be-analyzed namespaces-to-be-analyzed))
60+
(map :namespace))))
61+
62+
(defn ^:private build-context
4463
[options]
4564
(->> {}
4665
(->project-root options)
4766
(->config options)
48-
(->files options)
49-
(->namespaces options)))
67+
(->namespaces-and-dependencies options)
68+
(->namespaces-to-be-analyzed options)))
5069

5170
(defn configured?
5271
[project-root]

src/clj_depend/main.clj

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
:id :snapshot?
2525
:default false]])
2626

27-
(defn- exit!
27+
(defn ^:private exit!
2828
[exit-code message]
2929
(when message (println message))
3030
(System/exit (or exit-code 2)))

test/clj_depend/analyzer_test.clj

+17-15
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66

77
(deftest analyze-test
88
(testing "should not return violations when there are no circular dependencies and no layers/rules violated"
9-
(is (empty? (analyzer/analyze {:config {:layers {}
10-
:rules []}
11-
:dependencies-by-namespace {'foo.a.bar #{}
12-
'foo.b.bar #{'foo.a.bar}
13-
'foo.any #{'foo.a.bar}
14-
'foo.a-test #{'lib.x.y.z}}}))))
9+
(is (empty? (analyzer/analyze {:config {:layers {}
10+
:rules []}
11+
:namespaces-and-dependencies {'foo.a.bar #{}
12+
'foo.b.bar #{'foo.a.bar}
13+
'foo.any #{'foo.a.bar}
14+
'foo.a-test #{'lib.x.y.z}}
15+
:namespaces-to-be-analyzed #{'foo.a.bar 'foo.b.bar 'foo.any 'foo.a-test}}))))
1516

1617
(testing "should return violations when there are circular dependencies or layers/rules violated"
1718
(is (match? (m/in-any-order [{:namespace 'foo.a.bar
@@ -28,12 +29,13 @@
2829
{:namespace 'foo.a-test
2930
:dependency-namespace 'lib.x.y.z
3031
:message "\"foo.a-test\" should not depend on \"lib.x.y.z\""}])
31-
(analyzer/analyze {:config {:layers {:a {:defined-by ".*\\.a\\..*"
32-
:accesses-layers #{}}
33-
:b {:defined-by ".*\\.b\\..*"
34-
:accesses-layers #{}}}
35-
:rules [{:namespaces #{'foo.a-test} :should-not-depend-on #{'lib.x.y.z}}]}
36-
:dependencies-by-namespace {'foo.a.bar #{'foo.any}
37-
'foo.b.bar #{'foo.a.bar}
38-
'foo.any #{'foo.a.bar}
39-
'foo.a-test #{'lib.x.y.z}}})))))
32+
(analyzer/analyze {:config {:layers {:a {:defined-by ".*\\.a\\..*"
33+
:accesses-layers #{}}
34+
:b {:defined-by ".*\\.b\\..*"
35+
:accesses-layers #{}}}
36+
:rules [{:namespaces #{'foo.a-test} :should-not-depend-on #{'lib.x.y.z}}]}
37+
:namespaces-and-dependencies {'foo.a.bar #{'foo.any}
38+
'foo.b.bar #{'foo.a.bar}
39+
'foo.any #{'foo.a.bar}
40+
'foo.a-test #{'lib.x.y.z}}
41+
:namespaces-to-be-analyzed #{'foo.a.bar 'foo.b.bar 'foo.any 'foo.a-test}})))))

test/clj_depend/analyzers/circular_dependency_test.clj

-2
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@
88
:dependency-namespace 'foo.b
99
:message "Circular dependency between \"foo.a\" and \"foo.b\""}]
1010
(analyzers.circular-dependency/analyze 'foo.a
11-
#{'foo.b}
1211
{'foo.a #{'foo.b}
1312
'foo.b #{'foo.a}}))))
1413

1514
(testing "should not return violations when there is no circular dependency"
1615
(is (empty? (analyzers.circular-dependency/analyze 'foo.a
17-
#{'foo.b}
1816
{'foo.a #{'foo.b}
1917
'foo.b #{}})))))

0 commit comments

Comments
 (0)