Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# CHANGELOG

## Unreleased
* [#21](https://github.com/fabiodomingues/clj-depend/issues/22): Restrict namespaces to access others namespaces on the same layer
Comment thread
marco-carvalho marked this conversation as resolved.
Outdated

## 0.11.1 (2025-05-26)
* [#61](https://github.com/fabiodomingues/clj-depend/issues/61): Fix reflection warnings.
* [#61](https://github.com/fabiodomingues/clj-depend/issues/61): Fix reflection warnings.

## 0.11.0 (2024-04-19)
* [#56](https://github.com/fabiodomingues/clj-depend/issues/56): Added the `:only-ns-in-source-paths` attribute for when it is necessary to consider only namespaces in source paths as part of a layer.
Expand Down
4 changes: 3 additions & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ A map where each key is a layer and the value is a map, where:
- The layer is defined by a regex using the `:defined-by` key or a set of namespaces using the `:namespaces` key.
- The accesses allowed by it declared using the `:accesses-layers` key, or the accesses that are allowed to the layer using the `:accessed-by-layers` key. Since both keys accept a set of layers.
- `:only-ns-in-source-paths` optional, only considers namespaces in source paths as part of a layer. Available values: `true`, `false` with default value of `false`.
- `:access-peer-ns?` optional, controls whether namespaces within the same layer can access each other. Available values: `true`, `false` with default value of `true`. When `false`, namespaces in the same layer cannot depend on other namespaces in the same layer (except themselves).
Comment thread
marco-carvalho marked this conversation as resolved.
Outdated

Config example:
```clojure
{,,,
:layers {:controller {:defined-by ".*\\.controller\\..*"
:accesses-layers #{:logic :model}}
:accesses-layers #{:logic :model}
:access-peer-ns? false}
:logic {:defined-by ".*\\.logic\\..*"
:accesses-layers #{:model}}
:model {:defined-by ".*\\.model\\..*"
Expand Down
18 changes: 12 additions & 6 deletions src/clj_depend/analyzers/layer.clj
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@

(defn ^:private violate?
[config
{:keys [layer dependency-layer]}]
(and (not= layer dependency-layer)
(and (not (nil? layer))
(not (nil? dependency-layer)))
(or (dependency-layer-cannot-be-accessed-by-layer? config dependency-layer layer)
(layer-cannot-access-dependency-layer? config layer dependency-layer))))
{:keys [namespace dependency-namespace layer dependency-layer]}]
(let [same-namespace? (= namespace dependency-namespace)
same-layer? (= layer dependency-layer)
access-peer-ns? (get-in config [:layers layer :access-peer-ns?] true)]
Comment thread
marco-carvalho marked this conversation as resolved.
Outdated
(cond
same-namespace? false
(and same-layer? (not access-peer-ns?)) true
(and (not= layer dependency-layer)
(some? layer)
(some? dependency-layer))
(or (dependency-layer-cannot-be-accessed-by-layer? config dependency-layer layer)
(layer-cannot-access-dependency-layer? config layer dependency-layer)))))

(defn ^:private namespace-in-source-paths?
[namespace dependencies-by-namespace]
Expand Down
65 changes: 64 additions & 1 deletion test/clj_depend/analyzers/layer_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,67 @@
(testing "when a namespace that is not in the source-paths and that matches the defined-by regular expression of a layer that is not allowed to be accessed"
(let [violations (analyzers.layer/analyze config 'foo.b.bar {'foo.b.bar #{'foo.a.bar}})]
(testing "then no violations should have been returned"
(is (empty? violations))))))))
(is (empty? violations)))))))

(testing "Given a configuration with access-peer-ns? enabled (default behavior)"
(let [config {:layers {:controller {:defined-by ".*\\.controller\\..*"}}}]

(testing "when a namespace in the same layer depends on another namespace in the same layer"
(let [violations (analyzers.layer/analyze config
'my-app.controller.foo
{'my-app.controller.foo #{'my-app.controller.bar}})]
(testing "then no violations should be returned"
(is (= []
violations)))))

(testing "when a namespace depends on itself"
(let [violations (analyzers.layer/analyze config
'my-app.controller.foo
{'my-app.controller.foo #{'my-app.controller.foo}})]
(testing "then no violations should be returned"
(is (= []
violations)))))))

(testing "Given a configuration with access-peer-ns? disabled"
(let [config {:layers {:controller {:defined-by ".*\\.controller\\..*"
:access-peer-ns? false}}}]

(testing "when a namespace in the same layer depends on another namespace in the same layer"
(let [violations (analyzers.layer/analyze config
'my-app.controller.foo
{'my-app.controller.foo #{'my-app.controller.bar}})]
(testing "then a violation should be returned"
(is (= [{:namespace 'my-app.controller.foo
:dependency-namespace 'my-app.controller.bar
:layer :controller
:dependency-layer :controller
:message "\"my-app.controller.foo\" should not depend on \"my-app.controller.bar\" (layer \":controller\" on \":controller\")"}]
violations)))))

(testing "when a namespace depends on itself"
(let [violations (analyzers.layer/analyze config
'my-app.controller.foo
{'my-app.controller.foo #{'my-app.controller.foo}})]
(testing "then no violations should be returned"
(is (= []
violations)))))))

(testing "Given a configuration with access-peer-ns? explicitly enabled"
(let [config {:layers {:controller {:defined-by ".*\\.controller\\..*"
:access-peer-ns? true}}}]

(testing "when a namespace in the same layer depends on another namespace in the same layer"
(let [violations (analyzers.layer/analyze config
'my-app.controller.foo
{'my-app.controller.foo #{'my-app.controller.bar}})]
(testing "then no violations should be returned"
(is (= []
violations)))))

(testing "when a namespace depends on itself"
(let [violations (analyzers.layer/analyze config
'my-app.controller.foo
{'my-app.controller.foo #{'my-app.controller.foo}})]
(testing "then no violations should be returned"
(is (= []
violations))))))))