<commit_before>(ns shadow.cljs.devtools.errors
  (:require [clojure.repl :as repl]
            [clojure.string :as str]
            [clojure.spec.alpha :as s]
            [clojure.pprint :refer (pprint)]
            [shadow.build.closure :as closure]
            [shadow.build.api :as build]
            [shadow.build.ns-form :as ns-form]
            [shadow.build :as comp]
            [shadow.build.resolve :as resolve]
            [shadow.cljs.devtools.config :as config]
            [shadow.build.warnings :as w]
            [expound.alpha :as expound]
            [clojure.tools.logging :as log]
            [shadow.cljs.util :as util])
  (:import (java.io StringWriter FileNotFoundException)
           (clojure.lang ExceptionInfo ArityException)))

(declare error-format)

(def ignored-stack-elements
  #{"clojure.lang.RestFn"
    "clojure.lang.AFn"})

(defmulti ex-format
  (fn [w e]
    (class e)))

(defmethod ex-format :default [w e]
  ;; pretty similar to repl/pst
  (.write w (str (-> e class .getSimpleName) ": " (.getMessage e) "\n"))
  (let [stack
        (->> (.getStackTrace e)
             (remove #(contains? ignored-stack-elements (.getClassName %)))
             ;; limiting this is not ideal but we don't want to dump large stacks on the user
             ;; especially when analysis errors are really long but provide no useful insight
             (take 8)
             (map repl/stack-element-str))]

    (doseq [x stack]
      (.write w (str "\t" x "\n")))

    (when-let [cause (.getCause e)]
      (.write w "Caused by:\n")
      (error-format w cause)
      )))

(defmethod ex-format ArityException [w e]
  (.write w (.getMessage e)))

(defn get-tag [data]
  (or (:tag data)
      (when (= :reader-exception (:type data))
        ::reader-exception)
      (when (contains? data ::s/problems)
        ::s/problems)))

(defmulti ex-data-format
  (fn [w e data]
    (get-tag data))
  :default ::default)

(defmethod ex-data-format ::default [w e data]
  (doto w
    (.write (.getMessage e))
    (.write "\n")
    (.write (pr-str data))
    (.write "\n"))

  (ex-format w e))

(defn write-msg [w e]
  (.write w (str (str/trim (.getMessage e)) "\n")))

(defmethod ex-data-format ::comp/get-target-fn
  [w e {:keys [target build-id] :as data}]

  (if (instance? FileNotFoundException (.getCause e))
    ;; misspelled target
    (.write w (str "Target \"" (pr-str target) "\" for build " build-id " was not found. The built-in targets are:\n"
                   (->> [:browser
                         :browser-test
                         :node-script
                         :node-library
                         :npm-module
                         :karma
                         :bootstrap]
                        (map #(str "  - " %))
                        (str/join "\n"))))


    ;; print any other exception as is
    (do (write-msg w e)
        (error-format w (.getCause e)))
    ))

(defn spec-explain [w {::s/keys [problems value spec] :as data}]
  ;; FIXME: meh, no idea how to make spec errors easier to read
  #_(doseq [[in problems]
            (group-by :in problems)

            [val problems]
            (group-by :val problems)]

      (.write w (str "Value\n  "))
      (.write w (str (pr-str val)))
      (.write w "\nDid not conform to spec:\n")
      (doseq [{:keys [via path pred reason] :as problem} problems]
        (.write w "spec: ")
        (.write w (pr-str (s/abbrev pred)))
        (.write w "\n")
        ))

  (.write w (with-out-str
              (expound/printer data)
              #_(s/explain-out (select-keys data [::s/problems])))))

(defmethod ex-data-format ::ns-form/invalid-ns
  [w e {:keys [config] :as data}]
  (.write w "Invalid namespace declaration\n")
  (spec-explain w data))

(defmethod ex-data-format ::comp/config
  [w e {:keys [config] :as data}]
  (.write w "Invalid configuration\n")
  (spec-explain w data))

(defmethod ex-data-format ::resolve/missing-ns
  [w e data]
  (write-msg w e))

(defmethod ex-data-format ::resolve/missing-js
  [w e {:keys [require] :as data}]
  (write-msg w e)
  (when (util/is-package-require? require)
    (.write w (str "\n"<commit_msg>enhance missing-ns error to warn if there is a .clj file<commit_after>(ns shadow.cljs.devtools.errors
  (:require [clojure.repl :as repl]
            [clojure.string :as str]
            [clojure.spec.alpha :as s]
            [clojure.pprint :refer (pprint)]
            [shadow.build.closure :as closure]
            [shadow.build.api :as build]
            [shadow.build.ns-form :as ns-form]
            [shadow.build :as comp]
            [shadow.build.resolve :as resolve]
            [shadow.cljs.devtools.config :as config]
            [shadow.build.warnings :as w]
            [expound.alpha :as expound]
            [clojure.tools.logging :as log]
            [shadow.cljs.util :as util]
            [clojure.java.io :as io])
  (:import (java.io StringWriter FileNotFoundException)
           (clojure.lang ExceptionInfo ArityException)))

(declare error-format)

(def ignored-stack-elements
  #{"clojure.lang.RestFn"
    "clojure.lang.AFn"})

(defmulti ex-format
  (fn [w e]
    (class e)))

(defmethod ex-format :default [w e]
  ;; pretty similar to repl/pst
  (.write w (str (-> e class .getSimpleName) ": " (.getMessage e) "\n"))
  (let [stack
        (->> (.getStackTrace e)
             (remove #(contains? ignored-stack-elements (.getClassName %)))
             ;; limiting this is not ideal but we don't want to dump large stacks on the user
             ;; especially when analysis errors are really long but provide no useful insight
             (take 8)
             (map repl/stack-element-str))]

    (doseq [x stack]
      (.write w (str "\t" x "\n")))

    (when-let [cause (.getCause e)]
      (.write w "Caused by:\n")
      (error-format w cause)
      )))

(defmethod ex-format ArityException [w e]
  (.write w (.getMessage e)))

(defn get-tag [data]
  (or (:tag data)
      (when (= :reader-exception (:type data))
        ::reader-exception)
      (when (contains? data ::s/problems)
        ::s/problems)))

(defmulti ex-data-format
  (fn [w e data]
    (get-tag data))
  :default ::default)

(defmethod ex-data-format ::default [w e data]
  (doto w
    (.write (.getMessage e))
    (.write "\n")
    (.write (pr-str data))
    (.write "\n"))

  (ex-format w e))

(defn write-msg [w e]
  (.write w (str (str/trim (.getMessage e)) "\n")))

(defmethod ex-data-format ::comp/get-target-fn
  [w e {:keys [target build-id] :as data}]

  (if (instance? FileNotFoundException (.getCause e))
    ;; misspelled target
    (.write w (str "Target \"" (pr-str target) "\" for build " build-id " was not found. The built-in targets are:\n"
                   (->> [:browser
                         :browser-test
                         :node-script
                         :node-library
                         :npm-module
                         :karma
                         :bootstrap]
                        (map #(str "  - " %))
                        (str/join "\n"))))


    ;; print any other exception as is
    (do (write-msg w e)
        (error-format w (.getCause e)))
    ))

(defn spec-explain [w {::s/keys [problems value spec] :as data}]
  ;; FIXME: meh, no idea how to make spec errors easier to read
  #_(doseq [[in problems]
            (group-by :in problems)

            [val problems]
            (group-by :val problems)]

      (.write w (str "Value\n  "))
      (.write w (str (pr-str val)))
      (.write w "\nDid not conform to spec:\n")
      (doseq [{:keys [via path pred reason] :as problem} problems]
        (.write w "spec: ")
        (.write w (pr-str (s/abbrev pred)))
        (.write w "\n")
        ))

  (.write w (with-out-str
              (expound/printer data)
              #_(s/explain-out (select-keys data [::s/problems])))))

(defmethod ex-data-format ::ns-form/invalid-ns
  [w e {:keys [config] :as data}]
  (.write w "Invalid namespace declaration\n")
  (spec-explain w data))

(defmethod ex-data-format ::comp/config
  [w e {:keys [config] :as data}]
  (.write w "Invalid configuration\n")
  (spec-explain w data))

(defmethod ex-data-format ::resolve/missing-ns
  [w e {:keys [require] :as data}]
  (write-msg w e)
  (let [filename (str (util/ns->path require) ".clj")]
    (when-let [rc (io/resource filename)]
      (.write w (str "\"" filename "\" was found on the classpath. Should this be a .cljs file?\n"))
      )))

(defmethod ex-data-format ::resolve/missing-js
  [w e {:keys [require] :as data}]
  (write-msg w e)
  (when (util/is-package-require? require)
    (.write w (str "\n"
{'fraction_non_alphanumeric': 0.15250898134198632, 'fraction_numerical': 0.00023177656738903696, 'mean_word_length': 2.0217086834733893, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 14, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6875372', 'n_tokens_mistral': 2917, 'n_tokens_neox': 2768, 'n_words': 852}