Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ And it also bundles a few tiny linters of its own:

* [loc-per-ns](https://github.com/nedap/formatting-stack/blob/debdab8129dae7779d390216490625a3264c9d2c/src/formatting_stack/linters/loc_per_ns.clj) warns if a given NS surpasses a targeted LOC count.
* [ns-aliases](https://github.com/nedap/formatting-stack/blob/debdab8129dae7779d390216490625a3264c9d2c/src/formatting_stack/linters/ns_aliases.clj) warns if [Sierra's](https://stuartsierra.com/2015/05/10/clojure-namespace-aliases) aliasing guide is disregarded.
* [one-resource-per-ns](https://github.com/nedap/formatting-stack/blob/master/src/formatting_stack/linters/one_resource_per_ns.clj) warns if a Clojure namespace is defined in more than one file.

It is fully extensible: you can configure the bundled formatters, remove them, and/or add your own.

Expand Down Expand Up @@ -73,7 +74,7 @@ The general intent is to make formatting:
#### Coordinates

```clojure
[formatting-stack "1.0.1"]
[formatting-stack "2.0.0-alpha1"]
```

**Also** you have to add the latest [cider-nrepl](https://clojars.org/cider/cider-nrepl).
Expand Down
5 changes: 3 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
;; Please don't bump the library version by hand - use ci.release-workflow instead.
(defproject formatting-stack "1.0.1"
(defproject formatting-stack "2.0.0-alpha1"
;; Please keep the dependencies sorted a-z.
:dependencies [[clj-kondo "2019.05.19-alpha"]
[cljfmt "0.6.5" :exclusions [rewrite-clj]]
Expand Down Expand Up @@ -77,7 +77,8 @@
:dependencies [[com.nedap.staffing-solutions/utils.test "1.6.2"]]
:jvm-opts ["-Dclojure.core.async.go-checking=true"
"-Duser.language=en-US"]
:resource-paths ["test-resources"]}
:resource-paths ["test-resources-extra"
"test-resources"]}

:cider-nrepl {:plugins [[cider/cider-nrepl "0.21.1"]]}

Expand Down
6 changes: 5 additions & 1 deletion src/formatting_stack/branch_formatter.clj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
[formatting-stack.linters.kondo :as linters.kondo]
[formatting-stack.linters.loc-per-ns :as linters.loc-per-ns]
[formatting-stack.linters.ns-aliases :as linters.ns-aliases]
[formatting-stack.linters.one-resource-per-ns :as linters.one-resource-per-ns]
[formatting-stack.processors.cider :as processors.cider]
[formatting-stack.strategies :as strategies]
[medley.core :refer [mapply]]))
Expand Down Expand Up @@ -63,7 +64,10 @@
(assoc :strategies (conj default-strategies
strategies/exclude-edn
strategies/exclude-clj
strategies/exclude-cljc)))])
strategies/exclude-cljc)))]
(-> (linters.one-resource-per-ns/new {})
(assoc :strategies (conj default-strategies
strategies/files-with-a-namespace))))

(def default-processors
[(processors.cider/new {:third-party-indent-specs third-party-indent-specs})])
Expand Down
8 changes: 6 additions & 2 deletions src/formatting_stack/defaults.clj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
[formatting-stack.linters.kondo :as linters.kondo]
[formatting-stack.linters.loc-per-ns :as linters.loc-per-ns]
[formatting-stack.linters.ns-aliases :as linters.ns-aliases]
[formatting-stack.linters.one-resource-per-ns :as linters.one-resource-per-ns]
[formatting-stack.processors.cider :as processors.cider]
[formatting-stack.strategies :as strategies]))

Expand Down Expand Up @@ -63,8 +64,11 @@
(assoc :strategies (conj extended-strategies
strategies/exclude-edn
strategies/exclude-clj
strategies/exclude-cljc)))])
strategies/exclude-cljc)))
(-> (linters.one-resource-per-ns/new {})
(assoc :strategies (conj extended-strategies
strategies/files-with-a-namespace)))])

(defn default-processors [third-party-indent-specs]
[(processors.cider/new {:third-party-indent-specs third-party-indent-specs
:strategies extended-strategies})])
:strategies extended-strategies})])
61 changes: 61 additions & 0 deletions src/formatting_stack/linters/one_resource_per_ns.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
(ns formatting-stack.linters.one-resource-per-ns
"This linter ensures that there's exactly once classpath resource per namespace and extension.

Note that generally it's fine to define identically-named Clojure/Script namespaces with _different_ extensions,
so that is allowed."
(:require
[clojure.spec.alpha :as spec]
[clojure.string :as string]
[clojure.tools.namespace.file :as file]
[formatting-stack.protocols.linter :as linter]
[formatting-stack.util :refer [process-in-parallel!]]
[formatting-stack.util.ns :as util.ns]
[nedap.speced.def :as speced]
[nedap.utils.modular.api :refer [implement]]
[nedap.utils.spec.predicates :refer [present-string?]]))

(spec/def ::resource-path (spec/and present-string?
(complement #{\. \! \? \-})
(fn [x]
(re-find #"\.clj([cs])?$" x))))

(speced/defn ^::resource-path ns-decl->resource-path [^::util.ns/ns-form ns-decl, extension]
(-> ns-decl
second
str
munge
(string/replace "." "/")
(str extension)))

(speced/defn resource-path->filenames [^::resource-path resource-path]
(->> (-> (Thread/currentThread)
(.getContextClassLoader)
(.getResources resource-path))
(enumeration-seq)
(distinct) ;; just in case
(mapv str)))

(speced/defn analyze [^present-string? filename]
(for [extension [".clj" ".cljs" ".cljc"]
:let [decl (-> filename file/read-file-ns-decl)
resource-path (ns-decl->resource-path decl extension)
filenames (resource-path->filenames resource-path)]
:when (-> filenames count (> 1))]
{:extension extension
:ns-name (-> decl second)
:filenames filenames}))

(defn lint! [this filenames]
(->> filenames
(process-in-parallel! (fn [filename]
(->> filename
analyze
(run! (speced/fn [{:keys [^symbol? ns-name, ^coll? filenames]}]
(println "Warning: the namespace"
(str "`" ns-name "`")
"is defined over more than one file.\nFound:"
(->> filenames (interpose ", ") (apply str))))))))))

(speced/defn new [^map? opts]
(implement opts
linter/--lint! lint!))
6 changes: 5 additions & 1 deletion src/formatting_stack/project_formatter.clj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
[formatting-stack.linters.kondo :as linters.kondo]
[formatting-stack.linters.loc-per-ns :as linters.loc-per-ns]
[formatting-stack.linters.ns-aliases :as linters.ns-aliases]
[formatting-stack.linters.one-resource-per-ns :as linters.one-resource-per-ns]
[formatting-stack.processors.cider :as processors.cider]
[formatting-stack.strategies :as strategies]))

Expand Down Expand Up @@ -64,7 +65,10 @@
(assoc :strategies (conj default-strategies
strategies/exclude-edn
strategies/exclude-clj
strategies/exclude-cljc)))])
strategies/exclude-cljc)))
(-> (linters.one-resource-per-ns/new {})
(assoc :strategies (conj default-strategies
strategies/files-with-a-namespace)))])

(def default-processors
[(processors.cider/new {:third-party-indent-specs third-party-indent-specs})])
Expand Down
2 changes: 2 additions & 0 deletions test-resources-extra/orpn.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(ns orpn
"This namespace aides testing `formatting-stack.linters.one-resource-per-ns`")
2 changes: 2 additions & 0 deletions test-resources-extra/orpn.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(ns orpn
"This namespace aides testing `formatting-stack.linters.one-resource-per-ns`")
2 changes: 2 additions & 0 deletions test-resources-extra/orpn.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(ns orpn
"This namespace aides testing `formatting-stack.linters.one-resource-per-ns`")
2 changes: 2 additions & 0 deletions test-resources/orpn.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(ns orpn
"This namespace aides testing `formatting-stack.linters.one-resource-per-ns`")
2 changes: 2 additions & 0 deletions test-resources/orpn.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(ns orpn
"This namespace aides testing `formatting-stack.linters.one-resource-per-ns`")
2 changes: 2 additions & 0 deletions test-resources/orpn.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(ns orpn
"This namespace aides testing `formatting-stack.linters.one-resource-per-ns`")
10 changes: 10 additions & 0 deletions test/formatting_stack/test_helpers.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(ns formatting-stack.test-helpers
(:require
[nedap.speced.def :as speced])
(:import
(java.io File)))

(speced/defn filename-as-resource [^string? filename]
(str "file:" (-> filename
File.
.getAbsolutePath)))
43 changes: 43 additions & 0 deletions test/integration/formatting_stack/linters/one_resource_per_ns.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
(ns integration.formatting-stack.linters.one-resource-per-ns
(:require
[clojure.test :refer :all]
[formatting-stack.linters.one-resource-per-ns :as sut]
[formatting-stack.test-helpers :as test-helpers]
[formatting-stack.util :refer [rcomp]]))

(defn first! [coll]
{:pre [(->> coll count #{1})]}
(->> coll first))

(deftest analyze

(testing "This namespace is unambiguous"
(is (= (sut/analyze "test/integration/formatting_stack/linters/one_resource_per_ns.clj")
())))

Comment on lines +15 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about (is (empty? (sut/analyze ...))) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dunno. Asserting values is more specific than asserting computations (not good to author tests that pass on many inputs - seems sloppy); and also seeing the actual output of given function seems pretty useful.

(testing "Sample files are ambiguous (on a per-extension basis)"
(are [input extension expected] (testing input
(is (= (into #{}
(map test-helpers/filename-as-resource)
expected)
(->> input
sut/analyze
(filter (rcomp :extension #{extension}))
(first!)
:filenames
set)))
true)
"test-resources/orpn.clj" ".clj" #{"test-resources-extra/orpn.clj"
"test-resources/orpn.clj"}
"test-resources-extra/orpn.clj" ".clj" #{"test-resources-extra/orpn.clj"
"test-resources/orpn.clj"}

"test-resources/orpn.cljs" ".cljs" #{"test-resources-extra/orpn.cljs"
"test-resources/orpn.cljs"}
"test-resources-extra/orpn.cljs" ".cljs" #{"test-resources-extra/orpn.cljs"
"test-resources/orpn.cljs"}

"test-resources/orpn.cljc" ".cljc" #{"test-resources-extra/orpn.cljc"
"test-resources/orpn.cljc"}
"test-resources-extra/orpn.cljc" ".cljc" #{"test-resources-extra/orpn.cljc"
"test-resources/orpn.cljc"})))
22 changes: 22 additions & 0 deletions test/unit/formatting_stack/linters/one_resource_per_ns.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(ns unit.formatting-stack.linters.one-resource-per-ns
(:require
[clojure.test :refer :all]
[formatting-stack.linters.one-resource-per-ns :as sut]
[formatting-stack.test-helpers :as test-helpers]))

(deftest ns-decl->resource-path
(are [input expected] (testing input
(is (= expected
(sut/ns-decl->resource-path input ".clj")))
true)
'(ns unit) "unit.clj"
'(ns unit.formatting-stack.linters.one-resource-per-ns) "unit/formatting_stack/linters/one_resource_per_ns.clj"
'(ns foo!?) "foo_BANG__QMARK_.clj"))

(deftest resource-path->filenames
(are [input] (testing input
(is (= (-> "test/" (str input) test-helpers/filename-as-resource vector)
(sut/resource-path->filenames input)))
true)
"unit/formatting_stack/linters/one_resource_per_ns.clj"
"unit/formatting_stack/linters/ns_aliases.clj"))