问题
Tools.namespace find 1.4.1 版本无法处理未包含 ns
声明的源。
重放
给定文件 ./src/foo.clj
;; no ns declaration here
和测试脚本 ./test.clj
(require '[clojure.tools.namespace.find :as f]
'[clojure.java.io :as io])
(println "found nses:" (f/find-ns-decls-in-dir (io/file "src")))
我们可以看到,对于此场景,tools.namespace 1.4.0
返回一个空序列
$ clj -Sdeps '{:deps {org.clojure/tools.namespace {:mvn/version "1.4.0"}}}' -M --report stderr test.clj
found nses: ()
但 1.4.1 版本对这个相同的测试抛出了一个异常
$ clj -Sdeps '{:deps {org.clojure/tools.namespace {:mvn/version "1.4.1"}}}' -M --report stderr test.clj
{:clojure.main/message
"Execution error (NullPointerException) at clojure.tools.namespace.find/find-ns-decls-in-dir$fn (find.clj:93).\nCannot invoke \"clojure.lang.IObj.withMeta(clojure.lang.IPersistentMap)\" because \"x\" is null\n",
:clojure.main/triage
{:clojure.error/class java.lang.NullPointerException,
:clojure.error/line 93,
:clojure.error/cause
"Cannot invoke \"clojure.lang.IObj.withMeta(clojure.lang.IPersistentMap)\" because \"x\" is null",
:clojure.error/symbol
clojure.tools.namespace.find/find-ns-decls-in-dir$fn,
:clojure.error/source "find.clj",
:clojure.error/phase :execution},
:clojure.main/trace
{:via
[{:type clojure.lang.Compiler$CompilerException,
:message
"Syntax error macroexpanding at (/home/lee/proj/oss/-verify/tns-test/test.clj:4:1).",
:data
{:clojure.error/phase :execution,
:clojure.error/line 4,
:clojure.error/column 1,
:clojure.error/source
"/home/lee/proj/oss/-verify/tns-test/test.clj"},
:at [clojure.lang.Compiler load "Compiler.java" 7665]}
{:type java.lang.NullPointerException,
:message
"Cannot invoke \"clojure.lang.IObj.withMeta(clojure.lang.IPersistentMap)\" because \"x\" is null",
:at [clojure.core$with_meta__5485 invokeStatic "core.clj" 220]}],
:trace
[[clojure.core$with_meta__5485 invokeStatic "core.clj" 220]
[clojure.core$with_meta__5485 invoke "core.clj" 219]
[clojure.tools.namespace.find$find_ns_decls_in_dir$fn__1437
invoke
"find.clj"
93]
[clojure.core$keep$fn__8649 invoke "core.clj" 7409]
[clojure.lang.LazySeq sval "LazySeq.java" 42]
[clojure.lang.LazySeq seq "LazySeq.java" 51]
[clojure.lang.RT seq "RT.java" 535]
[clojure.core$seq__5467 invokeStatic "core.clj" 139]
[clojure.core$print_sequential invokeStatic "core_print.clj" 53]
[clojure.core$fn__7391 invokeStatic "core_print.clj" 174]
[clojure.core$fn__7391 invoke "core_print.clj" 174]
[clojure.lang.MultiFn invoke "MultiFn.java" 234]
[clojure.core$pr_on invokeStatic "core.clj" 3675]
[clojure.core$pr invokeStatic "core.clj" 3678]
[clojure.core$pr invoke "core.clj" 3678]
[clojure.lang.AFn applyToHelper "AFn.java" 154]
[clojure.lang.RestFn applyTo "RestFn.java" 132]
[clojure.core$apply invokeStatic "core.clj" 667]
[clojure.core$pr invokeStatic "core.clj" 3691]
[clojure.core$pr doInvoke "core.clj" 3678]
[clojure.lang.RestFn applyTo "RestFn.java" 139]
[clojure.core$apply invokeStatic "core.clj" 667]
[clojure.core$prn invokeStatic "core.clj" 3715]
[clojure.core$prn doInvoke "core.clj" 3715]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.core$apply invokeStatic "core.clj" 667]
[clojure.core$println invokeStatic "core.clj" 3734]
[clojure.core$println doInvoke "core.clj" 3734]
[clojure.lang.RestFn invoke "RestFn.java" 421]
[user$eval1480 invokeStatic "test.clj" 4]
[user$eval1480 invoke "test.clj" 4]
[clojure.lang.Compiler eval "Compiler.java" 7194]
[clojure.lang.Compiler load "Compiler.java" 7653]
[clojure.lang.Compiler loadFile "Compiler.java" 7591]
[clojure.main$load_script invokeStatic "main.clj" 475]
[clojure.main$script_opt invokeStatic "main.clj" 535]
[clojure.main$script_opt invoke "main.clj" 530]
[clojure.main$main invokeStatic "main.clj" 664]
[clojure.main$main doInvoke "main.clj" 616]
[clojure.lang.RestFn applyTo "RestFn.java" 137]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.main main "main.java" 40]],
:cause
"Cannot invoke \"clojure.lang.IObj.withMeta(clojure.lang.IPersistentMap)\" because \"x\" is null",
:phase :execution}}
Execution error (NullPointerException) at clojure.tools.namespace.find/find-ns-decls-in-dir$fn (find.clj:93).
Cannot invoke "clojure.lang.IObj.withMeta(clojure.lang.IPersistentMap)" because "x" is null
使用场景
Cljdoc 使用 tools.namespace 在 API 分析期间查找命名空间。
各种库包括各种奇怪的因素,包括无命名空间的源。
tools.namespace 的早期版本已经为我们处理了这些奇怪的因素。
如果我们决定 tools.namespace 不应该处理无命名空间的源,我们当然可以绕过这个问题。