Skip to content

Commit 99444e4

Browse files
committed
Trust ClojureScript :file analysis metadata
Updated ClojureScript's reader to use the :file metadata returned by analysis. It formerly set :file to the actual path of the :file it was analysing. This change matches the Clojure reader behaviour and allows for potemkin import-vars type manipulation of metadata to, for example, point at an alternate var's docs in a different source file. To verify readers are still returning data that we expect, I added some tests based on the existing playgound under test-sources. The tests are a good start but not comprehensive and should be extended as needed. Circleci config is included to automatically run tests on commit. While adding tests, I noticed small bugs and made corrections: 1. The ClojureScript reader now sorts protocol methods by name. This matches the behaviour of the Clojure reader. 2. The Clojure reader will now add a single var for each defrecord with the name of the defrecord. This matches the ClojureScript behaviour and is apparently the desired behaviour for cljdoc. For consistency and testability: - we are now removing keys with empty/nil values from reader analysis maps. - to be conistent with the ClojureScript reader, the Clojure reader no longer returns :file :line :column :end-column and :end-line on the namespace element. Notes: As part of this change, the ClojureScript reader will stop supressing protocol functions when the actual source does not match the :file metadata. This allows for a tool such as import-vars to point to, and have documented, an imported protocol function. No action was needed for the Clojure reader to make this work. This change was made a bit challenging due to cljdoc's usage of codox's root-dir and :file metadata: - The original intent of codox is that the config :root-dir point to the project (aka git) root of a project and that the :file metadata was simply what was returned by analysis. Codox added the :path to analysis metadata to resolve :file to the :root-dir. - Cljdoc does not set the :root-dir to the project root, ignores the :path and assumes that the :file is always relative to configured :source-path. Although this usage works for cljdoc, it did sprain my brain while making this change. - To compensate, I have been careful to ensure that :file continues to always be relative to the the configured :source-path. Another oddity was that altered :file metadata is being resolved to paths within the jar file when codox is called from cljdoc. This might be due to the way the classpath is setup when doing an analysis run. My change is coded to handle this situation when it arises.
1 parent 54d1a79 commit 99444e4

22 files changed

+534
-90
lines changed

.circleci/config.yml

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Clojure CircleCI 2.0 configuration file
2+
#
3+
# Check https://circleci.com/docs/2.0/language-clojure/ for more details
4+
#
5+
version: 2
6+
jobs:
7+
build:
8+
branches:
9+
only:
10+
- /cljs-proper.*/
11+
12+
docker:
13+
- image: circleci/clojure:tools-deps-1.10.0.442
14+
15+
working_directory: ~/repo
16+
17+
steps:
18+
- checkout
19+
20+
- restore_cache:
21+
keys:
22+
- v1-dependencies-{{ checksum "codox/deps.edn" }}
23+
- v1-dependencies- # fallback if cache not found
24+
25+
- run:
26+
name: Dump tool versions
27+
command: clojure -e '(println (System/getProperty "java.runtime.name") (System/getProperty "java.runtime.version") "\nClojure" (clojure-version))'
28+
working_directory: ~/repo/codox
29+
30+
- run:
31+
name: Dump classpath
32+
command: clojure -Spath
33+
working_directory: ~/repo/codox
34+
35+
- run:
36+
name: Run tests
37+
command: clojure -Atest --reporter documentation --plugin kaocha.plugin/junit-xml --junit-xml-file target/test-results/unit/results.xml
38+
working_directory: ~/repo/codox
39+
40+
- save_cache:
41+
paths:
42+
- ~/.m2
43+
key: v1-dependencies-{{ checksum "codox/deps.edn" }}
44+
45+
- store_test_results:
46+
path: codox/target/test-results
47+
48+
- store_artifacts:
49+
path: codox/target/test-results

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
pom.xml
22
pom.xml.asc
33
*jar
4+
.cpcache/
45
lib/
56
classes/
67
target/
8+
out/
79
checkouts/
810
.lein-*
911
.nrepl-port

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
### Notes about this fork
22

3-
- Focused on extracting structured data (EDN) from jars.
3+
[![CircleCI](https://circleci.com/gh/cljdoc/codox/tree/cljs-proper.svg?style=svg)](https://circleci.com/gh/cljdoc/codox/tree/cljs-proper)
4+
5+
- Focused on extracting structured data (EDN) from jars for cljdoc which makes use of work under codox subdir only.
46
- Any writing-related (HTML etc.) dependencies have been removed.
57
- Any remaining unused files are kept only to keep diff to mainline manageable.
68
- Various tweaks have been done to align the results of Clojure and ClojureScript analysis.
9+
- During dev, tests can be run via `clojure -A:test --watch`
710

811

912
---

codox/deps.edn

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
{:paths ["src" "resources" "test-sources"]
1+
{:paths ["src"]
22
:deps {org.clojure/clojure {:mvn/version "1.9.0"}
33
org.clojure/tools.namespace {:mvn/version "0.2.11"}
4-
org.clojure/clojurescript {:mvn/version "1.10.339"}}}
4+
org.clojure/clojurescript {:mvn/version "1.10.520"}}
5+
:aliases {:test
6+
{:extra-paths ["test" "test-sources"]
7+
:extra-deps {lambdaisland/kaocha {:mvn/version "0.0-413"}
8+
lambdaisland/kaocha-junit-xml {:mvn/version "0.0-70"}}
9+
:main-opts ["-m" "kaocha.runner"]}}}

codox/src/codox/main.clj

+17-11
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
(:require [clojure.string :as str]
55
[clojure.pprint]
66
[clojure.java.shell :as shell]
7+
[clojure.java.io :as io]
78
[codox.reader.clojure :as clj]
89
[codox.reader.clojurescript :as cljs]
9-
[codox.reader.plaintext :as text]))
10+
[codox.reader.plaintext :as text]
11+
[codox.utils :as util]))
1012

1113
(defn- writer [{:keys [writer]}]
1214
(let [writer-sym (or writer 'codox.writer.html/write-docs)
@@ -44,9 +46,11 @@
4446
(update-in [:members] add-var-defaults defaults))))
4547

4648
(defn- add-ns-defaults [namespaces defaults]
47-
(for [namespace namespaces]
48-
(-> (merge defaults namespace)
49-
(update-in [:publics] add-var-defaults defaults))))
49+
(if (seq defaults)
50+
(for [namespace namespaces]
51+
(-> (merge defaults namespace)
52+
(update-in [:publics] add-var-defaults defaults)))
53+
namespaces))
5054

5155
(defn- ns-matches? [{ns-name :name} pattern]
5256
(cond
@@ -100,13 +104,15 @@
100104
([]
101105
(generate-docs {}))
102106
([options]
103-
(let [options (merge defaults options)
104-
write-fn (writer options)
105-
namespaces (read-namespaces options)
106-
documents (read-documents options)]
107-
(write-fn (assoc options
108-
:namespaces namespaces
109-
:documents documents)))))
107+
(let [options (-> (merge defaults options)
108+
(update :root-path util/canonical-path)
109+
(update :souce-paths #(map util/canonical-path %)))
110+
write-fn (writer options)
111+
namespaces (read-namespaces options)
112+
documents (read-documents options)]
113+
(write-fn (assoc options
114+
:namespaces namespaces
115+
:documents documents)))))
110116

111117
(defn -main
112118
"The main entry point for reading API information from files in a directory.

codox/src/codox/reader/clojure.clj

+34-19
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
(:use [codox.utils :only (assoc-some update-some correct-indent)])
66
(:require [clojure.java.io :as io]
77
[clojure.tools.namespace.find :as ns]
8-
[clojure.string :as str]))
8+
[clojure.string :as str]
9+
[codox.utils :as util]))
910

1011
(defn try-require [namespace]
1112
(try
@@ -53,6 +54,14 @@
5354
(if-let [p (:protocol (meta var))]
5455
(some #{p} vars)))
5556

57+
(defn- include-record-factory-as-defrecord [var-meta]
58+
(let [n (str (:name var-meta))]
59+
(if (re-find #"map->\p{Upper}" n)
60+
(-> var-meta
61+
(assoc :name (symbol (subs n 5)))
62+
(dissoc :doc :arglists))
63+
var-meta)))
64+
5665
(defn- protocol-methods [protocol vars]
5766
(filter #(= protocol (:protocol (meta %))) vars))
5867

@@ -68,35 +77,40 @@
6877
(if (empty? delayed-errors)
6978
(:t ret))))
7079

71-
(defn- read-var [vars var]
72-
(-> (meta var)
73-
(select-keys [:name :file :line :arglists :doc :dynamic
74-
:added :deprecated :doc/format])
75-
(update-some :doc correct-indent)
76-
(assoc-some :type (var-type var)
77-
:type-sig (if (core-typed?) (core-typed-type var))
78-
:members (seq (map (partial read-var vars)
79-
(protocol-methods var vars))))))
80-
81-
(defn- read-publics [namespace]
80+
(defn- read-var [source-path vars var]
81+
(let [normalize (partial util/normalize-to-source-path source-path)]
82+
(-> (meta var)
83+
(include-record-factory-as-defrecord)
84+
(select-keys [:name :file :line :arglists :doc :dynamic
85+
:added :deprecated :doc/format])
86+
(update-some :doc correct-indent)
87+
(update-some :file normalize)
88+
(assoc-some :type (var-type var)
89+
:type-sig (if (core-typed?) (core-typed-type var))
90+
:members (seq (map (partial read-var source-path vars)
91+
(protocol-methods var vars))))
92+
util/remove-empties)))
93+
94+
(defn- read-publics [source-path namespace]
8295
(let [vars (sorted-public-vars namespace)]
8396
(->> vars
8497
(remove proxy?)
8598
(remove no-doc?)
8699
(remove (partial protocol-method? vars))
87-
(map (partial read-var vars))
100+
(map (partial read-var source-path vars))
88101
(sort-by (comp str/lower-case :name)))))
89102

90-
(defn- read-ns [namespace exception-handler]
103+
(defn- read-ns [namespace source-path exception-handler]
91104
(try-require 'clojure.core.typed.check)
92105
(when (core-typed?)
93106
(typecheck-namespace namespace))
94107
(try
95108
(require namespace)
96109
(-> (find-ns namespace)
97110
(meta)
111+
(dissoc :file :line :column :end-column :end-line)
98112
(assoc :name namespace)
99-
(assoc :publics (read-publics namespace))
113+
(assoc :publics (read-publics source-path namespace))
100114
(update-some :doc correct-indent)
101115
(list))
102116
(catch Exception e
@@ -148,8 +162,9 @@
148162
([paths {:keys [exception-handler]
149163
:or {exception-handler default-exception-handler}}]
150164
(mapcat (fn [path]
151-
(->> (io/file path)
152-
(find-namespaces)
153-
(mapcat #(read-ns % exception-handler))
154-
(remove :no-doc)))
165+
(let [path (util/canonical-path path)]
166+
(->> (io/file path)
167+
(find-namespaces)
168+
(mapcat #(read-ns % path exception-handler))
169+
(remove :no-doc))))
155170
paths)))

codox/src/codox/reader/clojurescript.clj

+34-17
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
[cljs.analyzer.api :as ana]
77
[cljs.closure]
88
[cljs.env]
9-
[clojure.string :as str]))
9+
[clojure.string :as str]
10+
[codox.utils :as util]))
1011

1112
(defn- cljs-filename? [filename]
1213
(or (.endsWith filename ".cljs")
@@ -51,25 +52,39 @@
5152
(multimethod? opts) :multimethod
5253
:else :var))
5354

54-
(defn- read-var [file vars var]
55-
(let [vt (var-type var)]
55+
(defn- read-var [source-path file vars var]
56+
(let [vt (var-type var)
57+
normalize (partial util/normalize-to-source-path source-path)]
5658
(-> var
57-
(select-keys [:name :line :arglists :doc :dynamic :added :deprecated :doc/format])
59+
(select-keys [:name :file :line :arglists :doc :dynamic :added :deprecated :doc/format])
5860
(update-some :name (comp symbol name))
5961
(update-some :arglists remove-quote)
6062
(update-some :doc correct-indent)
61-
(assoc-some :file (if (= vt :macro) (:file var) (.getPath file))
62-
:type vt
63-
:members (map (partial read-var file vars)
64-
(protocol-methods var vars))))))
65-
66-
(defn- read-publics [state namespace file]
67-
(let [vars (vals (ana/ns-publics state namespace))]
63+
(update-some :file normalize)
64+
(assoc-some :type vt
65+
:members (->> (protocol-methods var vars)
66+
(map (partial read-var source-path file vars))
67+
(map util/remove-empties)
68+
(map #(dissoc % :file :line))
69+
(sort-by :name)))
70+
util/remove-empties)))
71+
72+
(defn- unreferenced-protocol-fn?
73+
"Tools like potemkin import-vars can create a new function in one namespace point to an existing function within a protocol.
74+
In these cases, we want to include the new function."
75+
[source-path actual-file vars]
76+
(let [meta-file (util/normalize-to-source-path source-path (:file vars))
77+
actual-file (util/normalize-to-source-path source-path (str actual-file))]
78+
(and (:protocol vars) (= meta-file actual-file))))
79+
80+
(defn- read-publics [state namespace source-path file]
81+
(let [vars (vals (ana/ns-publics state namespace))
82+
unreferenced-protocol? (partial unreferenced-protocol-fn? source-path file)]
6883
(->> vars
69-
(remove :protocol)
7084
(remove :anonymous)
85+
(remove unreferenced-protocol?)
7186
(remove no-doc?)
72-
(map (partial read-var file vars))
87+
(map (partial read-var source-path file vars))
7388
(sort-by (comp str/lower-case :name)))))
7489

7590
(defn- analyze-file [file]
@@ -80,17 +95,19 @@
8095
(ana/analyze-file state file opts))
8196
state))
8297

83-
(defn- read-file [path file exception-handler]
98+
(defn- read-file [source-path file exception-handler]
8499
(try
85-
(let [source (io/file path file)
100+
101+
(let [source (io/file source-path file)
86102
ns-name (:ns (ana/parse-ns source))
87103
state (analyze-file source)]
88104
{ns-name
89105
(-> (ana/find-ns state ns-name)
90106
(select-keys [:name :doc])
91107
(update-some :doc correct-indent)
92108
(merge (-> ns-name meta (select-keys [:no-doc])))
93-
(assoc :publics (read-publics state ns-name file)))})
109+
(util/remove-empties)
110+
(assoc :publics (read-publics state ns-name source-path file)))})
94111
(catch Exception e
95112
(exception-handler e file))))
96113

@@ -129,7 +146,7 @@
129146
([paths {:keys [exception-handler]
130147
:or {exception-handler default-exception-handler}}]
131148
(mapcat (fn [path]
132-
(let [path (io/file path)
149+
(let [path (io/file (util/canonical-path path))
133150
file-reader #(read-file path % exception-handler)]
134151
(->> (find-files path)
135152
(map file-reader)

0 commit comments

Comments
 (0)