Welcome! Please see the About page for a little more info on how this works.

+1 vote
ago in Metadata by
retagged ago by

Preamble

Two thoughts:

Namespaces are not tied to files; they can be created and removed as desired. However, both idiomatically and through clojure.core, they are typically tied to files, being specified at the top of a file that shares the same name: src/noahtheduke/splint/rules.clj holds a single namespace noahtheduke.splint.rules, which is declared before all other forms. This is enforced in require and use calls: (require '[noahtheduke.foo.bar :as fb]) will find a file at src/noahtheduke/bar.clj and then once the file is loaded will attempt to find the namespace noahtheduke.bar and raise an error if it does not exist.

In the compiler, DefExpr will add to the created var's metadata location data in the form of :line, :column, and :file. The first two come from the seq and latter from *file*. This allows for introspection and analysis from tooling, and for better error handling in exceptions and messages.

Desired functionality

I have a collection of namespaces. I want to slurp the corresponding files (if they exist) to analyze and modify with rewrite-clj.

Discussion

Right now, I use metadata on vars to find relevant objects in code and then find the file that way:

(defn get-path-from-var [var']
  (let [f (:file (meta var'))
        path (or (some-> f
                         (#(.. (Thread/currentThread)
                               (getContextClassLoader)
                               (getResource %)))
                         (io/file)
                         (str))
                 f)]
    {:path path
     :file (when (and path (.exists (io/file path)))
             (slurp path))}))

However, that requires users to use the defcram macro I've written, which limits the potential for inline-usage of my primary macro compare-output (such as in a deftest).

I want to be able to use *ns* in compare-output to potentially find where the macro is being called, so I can perform the analysis and desired diffing. This would most easily be done by having location metadata on namespaces.

But more importantly than just my library, I think having location metadata on namespaces would be a genuine help for many tools (such as eastwood, tools.namespace, CIDER, kibit, splint, etc).

Solutions

I'm only going to discuss one solution because this is an Ask, not a jira ticket lol.

I think adding the location metadata in the ns macro is the best place to put it. The ns macro is the preferred way to create a new namespace. Creating namespaces with in-ns or create-ns requires additional steps to make the namespace usable, and neither is a macro to hold &form metadata, increasing the work of adding location metadata.

ago by
The patch for this would be fairly straightforward:

    diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj
    index be8e61c9..5e4bfc3e 100644
    --- a/src/clj/clojure/core.clj
    +++ b/src/clj/clojure/core.clj
    @@ -5849,6 +5849,12 @@
                    name)
             metadata   (when (map? (first references)) (first references))
             references (if metadata (next references) references)
    +        form-metadata (meta &form)
    +        metadata (merge (when (not= "NO_SOURCE_PATH" *file*)
    +                          {:line (:line form-metadata)
    +                           :column (:column form-metadata)
    +                           :file *file*})
    +                        metadata)
             name (if metadata
                    (vary-meta name merge metadata)
                    name)

1 Answer

+1 vote
ago by
...