 (defn emit-variadic-fn-method
   [{:keys [type name variadic params expr env recurs max-fixed-arity] :as f}]
   (emit-wrap env
-             (let [name (or name (gensym))
-                   mname (munge name)
-                   delegate-name (str mname "__delegate")]
-               (emitln "(function() { ")
-               (emits "var " delegate-name " = function (")
-               (doseq [param params]
-                 (emit param)
-                 (when-not (= param (last params)) (emits ",")))
-               (emitln "){")
-               (when recurs (emitln "while(true){"))
-               (emits expr)
-               (when recurs
-                 (emitln "break;")
-                 (emitln "}"))
-               (emitln "};")
-
-               (emitln "var " mname " = function (" (comma-sep
-                                                      (if variadic
-                                                        (concat (butlast params) ['var_args])
-                                                        params)) "){")
-               (when type
-                 (emitln "var self__ = this;"))
-               (when variadic
-                 (emits "var ")
-                 (emit (last params))
-                 (emitln " = null;")
-                 (emitln "if (arguments.length > " (dec (count params)) ") {")
-                 (let [a (emit-arguments-to-array (dec (count params)))]
-                   (emitln "  " (last params) " = new cljs.core.IndexedSeq(" a ",0);"))
-                 (emitln "} "))
-               (emits "return " delegate-name ".call(this,")
-               (doseq [param params]
-                 (emit param)
-                 (when-not (= param (last params)) (emits ",")))
-               (emits ");")
-               (emitln "};")
-
-               (emitln mname ".cljs$lang$maxFixedArity = " max-fixed-arity ";")
-               (emits mname ".cljs$lang$applyTo = ")
-               (emit-apply-to (assoc f :name name))
-               (emitln ";")
-               (emitln mname ".cljs$core$IFn$_invoke$arity$variadic = " delegate-name ";")
-               (emitln "return " mname ";")
-               (emitln "})()"))))
+    (let [name (or name (gensym))
+          mname (munge name)
+          delegate-name (str mname "__delegate")]
+      (emitln "(function() { ")
+      (emits "var " delegate-name " = function (")
+      (doseq [param params]
+        (emit param)
+        (when-not (= param (last params)) (emits ",")))
+      (emitln "){")
+      (when recurs (emitln "while(true){"))
+      (emits expr)
+      (when recurs
+        (emitln "break;")
+        (emitln "}"))
+      (emitln "};")
+
+      (emitln "var " mname " = function (" (comma-sep
+                                             (if variadic
+                                               (concat (butlast params) ['var_args])
+                                               params)) "){")
+      (when type
+        (emitln "var self__ = this;"))
+      (when variadic
+        (emits "var ")
+        (emit (last params))
+        (emitln " = null;")
+        (emitln "if (arguments.length > " (dec (count params)) ") {")
+        (let [a (emit-arguments-to-array (dec (count params)))]
+          (emitln "  " (last params) " = new cljs.core.IndexedSeq(" a ",0);"))
+        (emitln "} "))
+      (emits "return " delegate-name ".call(this,")
+      (doseq [param params]
+        (emit param)
+        (when-not (= param (last params)) (emits ",")))
+      (emits ");")
+      (emitln "};")
+
+      (emitln mname ".cljs$lang$maxFixedArity = " max-fixed-arity ";")
+      (emits mname ".cljs$lang$applyTo = ")
+      (emit-apply-to (assoc f :name name))
+      (emitln ";")
+      (emitln mname ".cljs$core$IFn$_invoke$arity$variadic = " delegate-name ";")
+      (emitln "return " mname ";")
+      (emitln "})()"))))
 
 (defmethod emit* :fn
   [{:keys [name env methods max-fixed-arity variadic recur-frames loop-lets]}]

      (when opts
        (str " " (pr-str (build-affecting-options opts)))))))

(defn compile-file*
  ([src dest] (compile-file* src dest nil))
  ([src dest opts]
   (env/ensure
     (with-core-cljs opts
       (fn []
         (when (or ana/*verbose* (:verbose opts))
           (util/debug-prn "Compiling" (str src)))
         (if-let [cached (and (= (:optimizations opts) :none)
                              (= (:ns (ana/parse-ns src)) 'cljs.core)
                              (io/resource "cljs/core.aot.js"))]
            ;; no need to bother with analysis cache reading, handled by
            ;; with-core-cljs
            (do
              (when (or ana/*verbose* (:verbose opts))
                (util/debug-prn "Using cached cljs.core" (str src)))
              (spit dest (slurp cached))
              (when (true? (:source-map opts))
                (spit (io/file (str dest ".map"))
                  (json/write-str
                    (assoc
                      (json/read-str (slurp (io/resource "cljs/core.aot.js.map")))
                      "file"
                      (str (io/file (util/output-directory opts) "cljs" "core.js"))))))
              (ana/parse-ns src dest nil))
            (with-open [out ^java.io.Writer (io/make-writer dest {})]
              (binding [*out* out
                        ana/*cljs-ns* 'cljs.user
                        ana/*cljs-file* (.getPath ^File src)
                        reader/*alias-map* (or reader/*alias-map* {})
                        ana/*cljs-static-fns* (or ana/*cljs-static-fns* (:static-fns opts))
                        *source-map-data* (when (:source-map opts)
                                            (atom
                                              {:source-map (sorted-map)
                                               :gen-col 0
                                               :gen-line 0}))]
                (emitln (compiled-by-string opts))
                (with-open [rdr (io/reader src)]
                  (loop [forms (ana/forms-seq* rdr (util/path src))
                         ns-name nil
                         deps nil]
                    (if (seq forms)
                      (let [env (ana/empty-env)
                            ast (ana/analyze env (first forms) nil opts)]
                        (emit ast)
                        (if (= (:op ast) :ns)
                          (recur (rest forms) (:name ast) (merge (:uses ast) (:requires ast)))
                          (recur (rest forms) ns-name deps)))
                      (let [sm-data (when *source-map-data* @*source-map-data*)
                            ret (merge
                                  {:ns (or ns-name 'cljs.user)
                                   :provides [ns-name]
                                   :requires (if (= ns-name 'cljs.core)
                                               (set (vals deps))
                                               (cond-> (conj (set (vals deps)) 'cljs.core)
                                                 (get-in @env/*compiler* [:options :emit-constants])
                                                 (conj 'constants-table)))
                                   :file dest
                                   :source-file src}
                                  (when sm-data
                                    {:source-map (:source-map sm-data)}))]
                        (when (and sm-data (= (:optimizations opts) :none))
                          (let [sm-file (io/file (str (.getPath ^File dest) ".map"))]
                            (emits "\n//# sourceMappingURL="
                              (or (:source-map-url opts) (.getName sm-file))
                              (if (true? (:source-map-timestamp opts))
                                (str "?rel=" (System/currentTimeMillis))
                                ""))
                            (spit sm-file
                              (sm/encode {(url-path src) (:source-map sm-data)}
                                {:lines (+ (:gen-line sm-data) 2)
                                 :file (url-path dest)
                                 :source-map-timestamp (:source-map-timestamp opts)
                                 :source-map-pretty-print (:source-map-pretty-print opts)}))))
                        (let [path (.getPath (.toURL ^File dest))]
                          (swap! env/*compiler* assoc-in [::compiled-cljs path] ret))
                        (let [{:keys [output-dir cache-analysis]} opts]
                          (when (and (true? cache-analysis) output-dir)
                            (ana/write-analysis-cache ns-name
                              (ana/cache-file src (ana/parse-ns src) output-dir :write)))
                          ret)))))))))))))

(defn requires-compilation?
  "Return true if the src file requires compilation."
  ([src dest] (requires-compilation? src dest nil))
  ([^File src ^File dest opts]
    (env/ensure
      (or (not (.exists dest))
          (> (.lastModified src) (.lastModified dest))
          (let [version' (util/compiled-by-version dest)
                version  (util/clojurescript-version)]
            (and version (not= version version')))
          (and opts
               (not= (build-affecting-options opts) (build-affecting-options (util/build-options dest))))
          (and opts
            (:source-map opts)
            (if (= (:optimizations opts) :none)
              (not (.exists (io/file (str (.getPath dest) ".map"))))
              (not (get-in @env/*compiler* [::compiled-cljs (.getAbsolutePath dest)]))))
          (let [{ns :ns} (ana/parse-ns src)
                {:keys [recompile visited]}
                (and *dependents* @*dependents*)]
            (and (contains? recompile ns)
                 (not (contains? visited ns))))))))

(defn compile-file
  "Compiles src to a file of the same name, but with a .js extension,
   in the src file's directory.

   With dest argument, write file to provided location. If the dest
   argument is a file outside the source tree, missing parent
   directories will be created. The src file will only be compiled if
   the dest file has an older modification time.

   Both src and dest may be either a String or a File.

   Returns a map containing {:ns .. :provides .. :requires .. :file ..}.
   If the file was not compiled returns only {:file ...}"
  ([src]
    (let [dest (rename-to-js src)]
      (compile-file src dest nil)))
  ([src dest]
    (compile-file src dest nil))
  ([src dest opts]
    {:post [map?]}
    (binding [ana/*file-defs* (atom #{})]
      (let [nses      (get @env/*compiler* ::ana/namespaces)
            src-file  (io/file src)
            dest-file (io/file dest)
            opts      (merge {:optimizations :none} opts)]
        (if (.exists src-file)
          (try
            (let [{ns :ns :as ns-info} (ana/parse-ns src-file dest-file opts)
                  opts (if (and (= ns 'cljs.core)
                                (not (false? (:static-fns opts))))
                         (assoc opts :static-fns true)
                         opts)]
              (if (or (requires-compilation? src-file dest-file opts)
                      (:force opts))
                (do
                  (util/mkdirs dest-file)
                  (when (and (get-in nses [ns :defs])
                             (not= 'cljs.core ns)
                             (not= :interactive (:mode opts)))
                    (swap! env/*compiler* update-in [::ana/namespaces] dissoc ns))
                  (let [ret (compile-file* src-file dest-file opts)]
                    (when *dependents*
                      (swap! *dependents*
                        (fn [{:keys [recompile visited]}]
                          {:recompile (into recompile
                                        (ana/ns-dependents ns
                                          (merge *inputs* nses)))
                           :visited (conj visited ns)})))
                    ret))
                (do
                  ;; populate compilation environment with analysis information
                  ;; when constants are optimized
                  (when (and (true? (:optimize-constants opts))
                             (nil? (get-in nses [ns :defs])))
                    (with-core-cljs opts (fn [] (ana/analyze-file src-file opts))))
                  ns-info)))
            (catch Exception e
              (throw (ex-info (str "failed compiling file:" src) {:file src} e))))
          (throw (java.io.FileNotFoundException. (str "The file " src " does not exist."))))))))

(defn cljs-files-in
  "Return a sequence of all .cljs and .cljc files in the given directory."
  [dir]
  (filter
    #(let [name (.getName ^File %)]
      (and (or (.endsWith name ".cljs")
               (.endsWith name ".cljc"))
           (not= \. (first name))
           (not (contains? cljs-reserved-file-names name))))
    (file-seq dir)))

(defn compile-root
  "Looks recursively in src-dir for .cljs files and compiles them to
   .js files. If target-dir is provided, output will go into this
   directory mirroring the source directory structure. Returns a list
   of maps containing information about each file which was compiled
   in dependency order."
  ([src-dir]
     (compile-root src-dir "out"))
  ([src-dir target-dir]
     (compile-root src-dir target-dir nil))
  ([src-dir target-dir opts]
     (swap! env/*compiler* assoc :root src-dir)
     (let [src-dir-file (io/file src-dir)
           inputs (deps/dependency-order
                    (map #(ana/parse-ns %)
                      (cljs-files-in src-dir-file)))]
       (binding [*inputs* (zipmap (map :ns inputs) inputs)]
         (loop [inputs (seq inputs) compiled []]
           (if inputs
             (let [{:keys [source-file] :as ns-info} (first inputs)
                   output-file (util/to-target-file target-dir ns-info)
                   ijs (compile-file source-file output-file opts)]
               (recur
                 (next inputs)
                 (conj compiled
                   (assoc ijs :file-name (.getPath output-file)))))
             compiled))))))

;; TODO: needs fixing, table will include other things than keywords - David


      (emits-keyword keyword)
      (emits ";\n"))))

#?(:clj
   (defn emit-constants-table-to-file [table dest]
     (io/make-parents dest)
     (with-open [out ^java.io.Writer (io/make-writer dest {})]
       (binding [*out* out]
         (emit-constants-table table)))))
