Slamhound fails to reconstruct my ns forms - clojure

I have created a project with two namespaces, familja-moderne.core and familja-moderne.visualization.svg.
src/familja_moderne/core.clj
(ns familja-moderne.core
(:require [clojure.set :as set])
(:import (java.io ByteArrayInputStream)))
(def try-out
[(set/map-invert
{:1 :2
:3 :4})
(ByteArrayInputStream. (.getBytes "myBytes"))
(familja-moderne.visualization.svg/points heists)])
src/familja_moderne/visualization/svg.clj
(ns familja-moderne.visualization.svg
(:refer-clojure :exclude [max min]))
(def some-map {:this :that
:foo :bar})
(def many-dependencies
{:something (ByteArrayInputStream. (.getBytes "something"))
:another-map (set/map-invert some-map)})
When I run
lein slamhound code/familja-moderne/src/familja_moderne/visualization/svg.clj
I get
WARNING: ex-info already refers to: #'clojure.core/ex-info in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-info
WARNING: ex-data already refers to: #'clojure.core/ex-data in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-data
which I don't understand but it works and my ns form is reconstructed as
(ns familja-moderne.visualization.svg
(:require [clojure.set :as set])
(:import (java.io ByteArrayInputStream))
(:refer-clojure :exclude [max min]))
Running
lein slamhound code/familja-moderne/src/familja_moderne/core.clj
fails and I get the following messages
WARNING: ex-info already refers to: #'clojure.core/ex-info in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-info
WARNING: ex-data already refers to: #'clojure.core/ex-data in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-data
Failed to reconstruct: #<File code/familja-moderne/src/familja_moderne/core.clj>
java.lang.ClassNotFoundException: familja-moderne.visualization.svg, compiling:(NO_SOURCE_PATH:0:0)
Running
lein slamhound code/familja-moderne/src/familja_moderne/
which is supposed to reconstruct the ns forms in both namespaces results in something different
WARNING: ex-info already refers to: #'clojure.core/ex-info in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-info
WARNING: ex-data already refers to: #'clojure.core/ex-data in namespace: slingshot.ex-info, being replaced by: #'slingshot.ex-info/ex-data
Failed to reconstruct: #<File code/familja-moderne/src/familja_moderne/core.clj>
Couldn't resolve familja-moderne.visualization.svg, got as far as {:import #{java.io.ByteArrayInputStream}, :alias {clojure.set set}, :old {:load nil, :exclude {}, :xrefer #{}, :require #{}, :refer-all #{}, :verbose #{}, :rename {}, :alias {clojure.set set}, :reload #{}, :reload-all #{}, :gen-class nil, :import #{java.io.ByteArrayInputStream}, :refer {}}, :meta nil, :name familja-moderne.core}
If I remove any reference to familja-moderne.visualization.svg from familja-moderne.core it works OK.

I was having the same issue and opened a PR to Slamhound with a fix: https://github.com/technomancy/slamhound/pull/87.
You can try it by doing a lein install from the dotted-alias branch in my fork.
My theory is that this issue happens because of the clojure bug CLJ-1403 which makes the exception to be thrown when Slamhound is trying to "regrow" the ns form.
Hope that helps!

Related

How do I invoke a resolved function in a Compojure view?

I have the following code as a test:
(defn test-fn [] (println "This is a test"))
(def my-test "test")
((resolve (symbol (str my-test "-fn"))))
Which runs as I would expect producing This is a test.
So I put it inside of a separate view like so:
(ns my-test.template-views
(:require
[hiccup.core :refer :all]
[hiccup.page :refer :all]
[my-test.home-views :refer :all]
[my-test.page1-views :refer :all]))
(defn template-body
[uri]
(html5 {:lang "en"}
[:body
(let [the-page (if (> (count uri) 1)
(clojure.string/replace uri #"/" "")
"home")]
((resolve (symbol (str the-page "-body")))))]))
Which gets called from Compojure like this:
(ns the-test.reporting-dashboard
(:gen-class)
(:require
[the-test.template-views :refer :all]
[compojure.core :refer [defroutes GET POST context]]
[compojure.route :as route]
[org.httpkit.server :refer [run-server]]
))
(defn wrap-request
[handler]
(fn [request]
(let [{remote-addr :remote-addr uri :uri scheme :scheme request-method :request-method} request]
(println (str "REQUEST: " request)))
(handler request)))
(defroutes app
(wrap-request
(GET "/" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (home-views/home-body (:uri request))}))
(wrap-request
(GET "/foo" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (template-body (:uri request))}))
(route/resources "/")
(route/not-found {:status 404
:headers {"Content-Type" "text/html"}
:body "<h1>Not Found</h1>"}))
When I call https//mydomain.tld/foo I get a java.lang.NullPointerException:
user=> Sat Apr 24 17:59:16 MDT 2021 [worker-2] ERROR - GET /foo
java.lang.NullPointerException
at the-test.template_views$template_body.invokeStatic(template_views.clj:14)
at the-test.template_views$template_body.invoke(template_views.clj:12)
at the-test.my_test$fn__269541.invokeStatic(my_test.clj:49)
at the-test.my_test$fn__269541.invoke(my_test.clj:46)
at compojure.core$wrap_response$fn__269102.invoke(core.clj:158)
at compojure.core$wrap_route_middleware$fn__269086.invoke(core.clj:128)
at compojure.core$wrap_route_info$fn__269091.invoke(core.clj:137)
at compojure.core$wrap_route_matches$fn__269095.invoke(core.clj:146)
at the-test.my_test$wrap_request$fn__269531.invoke(my_test.clj:22)
at compojure.core$routing$fn__269110.invoke(core.clj:185)
at clojure.core$some.invokeStatic(core.clj:2705)
at clojure.core$some.invoke(core.clj:2696)
at compojure.core$routing.invokeStatic(core.clj:185)
at compojure.core$routing.doInvoke(core.clj:182)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invokeStatic(core.clj:669)
at clojure.core$apply.invoke(core.clj:662)
at compojure.core$routes$fn__269114.invoke(core.clj:192)
at org.httpkit.server.HttpHandler.run(RingHandler.java:117)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
What is going on that this function is no longer able to be called when in Compojure?
Not sure about the root cause, but I was able to find a similar, minimal example and a workaround.
I created a simple project with lein new app demo, then replaced the code for core.clj with:
(ns demo.core
(:require [clojure.string :refer :all]))
(defn -main
[& args]
(println ((resolve 'capitalize) "hello")))
Running this with lein run crashes:
$ lein run
WARNING: reverse already refers to: #'clojure.core/reverse in namespace: demo.core, being replaced by: #'clojure.string/reverse
WARNING: replace already refers to: #'clojure.core/replace in namespace: demo.core, being replaced by: #'clojure.string/replace
Syntax error (NullPointerException) compiling at (/tmp/form-init1769060018979063159.clj:1:73).
null
Full report at:
/tmp/clojure-17609112051049758700.edn
The error report shows something about an error in Compiler.java so maybe it's some lazy initialization issue or just a bug. It seems to me the var cannot be resolved at the time you are attempting to call it as a function, hence the NullPointerException.
A workaround would be to use ns-resolve instead:
(ns demo.core
(:require [clojure.string :refer :all]))
(defn -main
[& args]
(println ((ns-resolve 'clojure.string 'capitalize) "hello")))
The above works as expected:
$ lein run
WARNING: reverse already refers to: #'clojure.core/reverse in namespace: demo.core, being replaced by: #'clojure.string/reverse
WARNING: replace already refers to: #'clojure.core/replace in namespace: demo.core, being replaced by: #'clojure.string/replace
Hello
See if you can replace your usage of resolve in the view with ns-resolve if you know the namespace where the symbol needs to be looked-up.

lein uberjar cannot find an implementation defined in deftype

In our Clojure codebase we have a protocol:
(ns project.repository)
(defprotocol Repository
(index [this fields unique]))
A type
(ns project.mongo (:require
[monger.collection :as mc]
[monger.core :as mg]
[project.repository :refer :all]))
(deftype MongoRepository [db collection-name]
Repository
(index [this fields unique]
(mc/ensure-index db collection-name fields {:unique unique})))
(defn mongo-repository [db coll] (MongoRepository. db coll))
(def mongo-db ((mg/connect-via-uri "mongodb://127.0.0.1/bots") :db))
And an instantiation
(ns project.users (:require
[lp-bots.storages.repository :refer :all]
[lp-bots.storages.mongo :refer [mongo-repository mongo-db]]))
(def users-storage (mongo-repository mongo-db "users"))
(index users-storage [:key1 :key2] true)
This works fine when used interactively from REPL or launched with lein run, but lein uberjar invariably throws an exception:
Exception in thread "main" java.lang.ExceptionInInitializerError, compiling:(/tmp/form-init118199196859405970.clj:1:72)
...
Caused by: java.lang.ExceptionInInitializerError
...
Caused by: java.lang.IllegalArgumentException: No implementation of method: :index of protocol: #'project.repository/Repository found for class: project.mongo.MongoRepository
at clojure.core$_cache_protocol_fn.invokeStatic(core_deftype.clj:568)
at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:560)
at project.repository$fn__557$G__514__566.invoke(repository.clj)
at project.users__init.load(Unknown Source)
at project.users__init.<clinit>(Unknown Source)
The weirdest part is that the problem goes away when (index) is called in a (let):
(def users-storage
(let [u (mongo-repository mongo-db "leads")]
(index u [:key1 :key2] true)
u))
Any thoughts on what might be causing the difference?
I would suspect that it's something to do with trying to connect to MongoDB when compiling the project.mongo ns, or the project.users ns. These lines should probably be in a function call that's called at startup, rather than as the code is loaded:
(def mongo-db ((mg/connect-via-uri "mongodb://127.0.0.1/bots") :db))
and
(index users-storage [:key1 :key2] true)

Clojurescript clojure.set not working

I've referenced ClojureScript clojure.set? and can only get the docstring to come up, but actually running any of the clojure.set function doesn't work. Using ClojureScript 0.0-3126 on Opera 28.0 and/or Chrome 41.0.2272.101 m on Windows 7 x64.
Any ideas on how to correct this?
thanks
ClojureScript:cljs.user> (require '[clojure.set])
nil
ClojureScript:cljs.user> (doc clojure.set/union)
-------------------------
clojure.set/union
([] [s1] [s1 s2] [s1 s2 & sets])
Return a set that is the union of the input sets
nil
ClojureScript:cljs.user> (clojure.set/union #{:a} #{:b})
TypeError: Cannot read property 'union' of undefined
TypeError: Cannot read property 'union' of undefined
at eval (eval at <anonymous> (http://localhost:8888/js/coreweb.js:45250:260), <anonymous>:1:100)
at eval (eval at <anonymous> (http://localhost:8888/js/coreweb.js:45250:260), <anonymous>:9:3)
at eval (ev al at <anonymous> (http://localhost:8888/js/coreweb.js:45250:260), <anonymous>:14:4)
at http://localhost:8888/js/coreweb.js:45250:255
at clojure$browser$repl$evaluate_javascript (http://localhost:8888/js/coreweb.js:45256:4)
at Object.callback (http://localhost:8888/js/coreweb.js:45421:181)
at goog.messaging.AbstractChannel.deliver (http://localhost:8888/js/coreweb.js:42499:13)
at goog.net.xpc.CrossPageChannel.xpcDeliver (http://localhost:8888/js/coreweb.js:43463:14)
at Function.goog.net.xpc.NativeMessagingTransport.messageReceived_ (http://localhost:8888/js/coreweb.js:42859:13)
at Object.goog.events.fireListener (http://localhost:8888/js/coreweb.js:39835:21)
That's more personal research than an answer. Still, I did reproduce your problem in Chrome (on MAC) taking the ClojureScript Quick Start.
If the cljs server repl file is as follow (i.e. there is no require on clojure.set):
(ns hello-world.core
(:require [clojure.browser.repl :as repl]))
(defonce conn
(repl/connect "http://localhost:9000/repl"))
(enable-console-print!)
(println "Hello world!")
and the repl.clj is:
(require 'cljs.repl)
(require 'cljs.closure)
(require 'cljs.repl.browser)
(cljs.closure/build "src"
{:main 'hello-world.core
:output-to "out/main.js"
:verbose true})
(cljs.repl/repl (cljs.repl.browser/repl-env)
:watch "src"
:output-dir "out")
Then starting the repl in a terminal with:
rlwrap java -cp cljs.jar:src clojure.main repl.clj
And connecting the browser on http://localhost:9000, then this works:
ClojureScript:cljs.user> (require '[clojure.set])
nil
ClojureScript:cljs.user> (clojure.set/union #{6} #{9} #{7})
#{7 6 9}
Now, if you reload the server page http://localhost:9000, and try again, I had the problem (doc is available on the symbol, but the var is not there anymore):
ClojureScript:cljs.user> (doc clojure.set/union)
-------------------------
clojure.set/union
([] [s1] [s1 s2] [s1 s2 & sets])
Return a set that is the union of the input sets
nil
ClojureScript:cljs.user> (clojure.set/union #{6} #{9} #{7})
TypeError: Cannot read property 'union' of undefined
TypeError: Cannot read property 'union' of undefined
at eval (eval at <anonymous> (http://localhost:9000/out/clojure/browser/repl.js:42:272), <anonymous>:1:100)
If you modify the cljs server file to require clojure.set, i.e
(ns hello-world.core
(:require [clojure.browser.repl :as repl]
[clojure.set]))
Then, it looks like you still need to require clojure.set from the repl in order to use its functions, but then, you can reload the server page and it still works in the repl.

Clojure require namespace: "Don't know how to create ISeq from: clojure.lang.Keyword"

I'm trying to split code in 2 files, each with it's own namespace. Following this tutorial.
But I get this error:
Exception in thread "main" java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Keyword
I think it's because the namespace being included is not recognised properly.
Main file:
(ns mytest2.handler
(:use compojure.core)
(:require [compojure.handler :as handler]
[compojure.route :as route]
[mytest2.views :as foo] ;<-- line causing error
[hiccup.core :refer (html)])
)
(defn layout [title & content]
(html
[:head [:title title]]
[:body content]))
(defn main-page []
(layout "My Blog"
[:h1 "My Blog"]
[:p "Welcome to my page"]))
(defroutes app-routes
(GET "/" [] (main-page))
(route/resources "/")
(route/not-found "Not Found"))
(def app
(handler/site app-routes))
; (println (seq (.getURLs (java.lang.ClassLoader/getSystemClassLoader))))
Second file:
(ns mytest2.views
:require [hiccup.core :refer (html)]
)
(defn layout [title & content]
(html
[:head [:title title]]
[:body content]))
(defn main-page []
(layout "My Blog"
[:h1 "My Blog"]
[:p "Welcome to my page"]))
(note I copied the functions from mytest2.views in mytest2.handler for testing. They're not supposed to be in mytest2.handler).
Paths of files:
/mytest2/src/mytest2/handler.clj
/mytest2/src/mytest2/views.clj
(where first mytest2 is the name of the project, and the second is part of the path- automatically created by lein).
As you see in the first file I printed the class path to verify that /mytest2/src/mytest2/ is included, and yes it is.
Received the same error from trying to use :refer :all in Clojurescript, which apparently is against the rules.
You missed some brackets in your original code
;; wrong
(ns mytest2.views
:require [hiccup.core :refer [html]])
There is just one pair of brackets missing. Do it as in your Main file:
;; Done right!
(ns mytest2.views
(:require [hiccup.core :refer [html]]))
I am not familar with Compojure so I do not know what you have to require. But you need to add the bracket around :require.

ClojureScript: migrating from fetch to shoreleave

As webnoir got deprecated I've decided to migrate from fetch as described in docs, but for some reason shoreleave.remotes in not being compiled to Js and as a result I get
goog.require could not find: shoreleave.remotes.http_rpc
I've tried both remote-callback and remotes.macros. My ClojureScript namespace goes like this
(ns my.cljs.ns
(:require [shoreleave.remotes.http-rpc :as rpc])
(:require-macros [shoreleave.remotes.macros :as fm])
and backend
(ns my.backend
(:use my.views
(:require [compojure.route :as route]
[compojure.handler :as handler]
[cemerick.shoreleave.rpc :as rpc]))
)
(rpc/defremote get-data [last-changed]
[:foo]
)
(defroutes my-routes
(GET "/" [] (intro-view))
(route/files "/" {:root "resources/public"})
(route/not-found "<h1>Page not found</h1>"))
(def my-routing (-> #'my-routes
rpc/wrap-rpc
handler/site))
Any ideas how to get it working?
UPDATE: I found out that for some reason goog.require('shoreleave.remotes.http_rpc') goes before goog.provide('shoreleave.remotes.http_rpc')
it was a problem with incremental complition in lein (see bug CLJS-282), adding ':incremental false' in project.clj fixed my problem:
:cljsbuild {
:builds [{
:incremental false
:jar true
:source-path "src-cljs"
:compiler {
:output-to "resources/public/js/js.js"
:optimizations :whitespace
:pretty-print true}
}]}
I just solved this problem by adding two dependencies in project.clj.
[shoreleave "0.3.0"]
[shoreleave/shoreleave-remote "0.3.0"]