-
-
Save Chouser/5796967 to your computer and use it in GitHub Desktop.
(ns n01se.externs-for-cljs | |
(:require [clojure.java.io :as io] | |
[cljs.compiler :as comp] | |
[cljs.analyzer :as ana])) | |
(defn read-file [file] | |
(let [eof (Object.)] | |
(with-open [stream (clojure.lang.LineNumberingPushbackReader. (io/reader file))] | |
(vec (take-while #(not= % eof) | |
(repeatedly #(read stream false eof))))))) | |
(defn file-analysis [file] | |
(binding [ana/*cljs-ns* 'cljs.user | |
ana/*cljs-file* file] | |
(mapv #(ana/analyze (ana/empty-env) %) (read-file file)))) | |
(defn flat-file-analysis [file] | |
(mapcat #(tree-seq :children :children %) | |
(file-analysis file))) | |
(defn get-vars-used [ffa] | |
(->> ffa | |
(filter #(and (= (:op %) :var) (-> % :info :ns))) | |
(map #(-> % :info :name)) | |
distinct)) | |
(defn var-defined? [sym] | |
(contains? (:defs (get @ana/namespaces (symbol (namespace sym)))) | |
(symbol (name sym)))) | |
(defn get-undefined-vars [ffa] | |
(remove var-defined? (get-vars-used ffa))) | |
(defn externs-for-var [sym] | |
(if (= "js" (namespace sym)) | |
(format "var %s={};\n" (name sym)) | |
(format "var %s={};\n%s.%s=function(){};\n" | |
(namespace sym) (namespace sym) (name sym)))) | |
(defn get-interop-used [ffa] | |
(->> ffa | |
(filter #(and (= (:op %) :dot))) | |
(map #(or (:method %) (:field %))) | |
distinct)) | |
(defn externs-for-interop [sym] | |
(format "DummyExternClass.%s=function(){};\n" sym)) | |
(defn externs-for-cljs [file] | |
(swap! ana/namespaces empty) | |
(ana/analyze-file "cljs/core.cljs") | |
(let [ffa (flat-file-analysis file)] | |
(apply str | |
(concat | |
(map externs-for-var (get-undefined-vars ffa)) | |
["var DummyExternClass={};\n"] | |
(map externs-for-interop (get-interop-used ffa)))))) | |
;; (externs-for-cljs "/home/chouser/proj/cljs-example/src/main.cljs") |
cemerick
commented
Jun 17, 2013
- Does this really work? ;-)
- Why the hell doesn't gclosure just do this automatically‽
- It's sufficient for some input sources, but I've probably missed some cases.
- Maybe because in ClojureScript we can tell the difference between interop, vars, and locals more easily than in JavaScript? Not sure.
This is terrific - just saved me some serious headache. Much appreciated!
It's probably worthwhile to tidy this up and get it into the compiler.
This is a nice example of using the CLJS analyzer, too. Thanks @Chouser.
I'm a bit lost here; Without knowing ClojureScript (just got it working on my Windows machine) - how do I run this script to generate the externs? (Also: it is meant to create externals for plain .js files, right?)
What version of clojurescript was this written/work against? I've just run it on some code of my own and I'm getting:
ClassCastException cljs.analyzer$reify__4241 cannot be cast to clojure.lang.Atom clojure.core/swap! (core.clj:2160)
I'm using 0.0-2202.
Also, for @dertseha there's a blog on using this at http://www.dotkam.com/2013/07/15/clojurescript-use-any-javascript-library/