commit c2c2d791ebb95be443f53cad96ac81fa750cd807 Author: Jonas Enlund Date: Sun Mar 4 18:41:53 2012 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d139ad9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/pom.xml +*jar +/lib +/classes +/native +/.lein-failures +/checkouts +/.lein-deps-sum +*~ diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a272d4 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# kibit + +*There's a function for that!* + +`kibit` is a static code analyzer for Clojure which uses the +[`core.logic`](https://github.com/clojure/core.logic) unifier to +search for patterns of code for which there might exist a more +idiomatic function or macro. For example if kibit finds the code + + (if (some test) + (some action) + nil) + +it will make the suggestion to use the `while` macro instead of `if`. + +## Usage + +Add + + :dev-dependencies [... + [kibit "0.0.1"] + ...] + +to your `project.clj` file and run + + lein kibit + +to analyze your namespaces. + +## Contributing + +It is very easy to write new patterns for `kibit` to look for. Take a +look at [`arithmetic.clj`] how new patterns are created. If you know +of a reaccuring pattern of code that can be simplified, please +consider sending me a pull request. + +Bugs can be reported using the github bug tracker. + +## TODO + +* Figure out how to report line numbers. +* More rules + +## License + +Copyright (C) 2012 Jonas Enlund + +Distributed under the Eclipse Public License, the same as Clojure. diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..a490469 --- /dev/null +++ b/project.clj @@ -0,0 +1,7 @@ +(defproject kibit "0.0.1-SNAPSHOT" + :description "There's a function for that!" + :dependencies [[org.clojure/clojure "1.3.0"] + [org.clojure/core.logic "0.6.7"] + [org.clojure/tools.namespace "0.1.2"]] + :eval-in-leiningen true + :warn-on-reflection false) \ No newline at end of file diff --git a/src/kibit/arithmetic.clj b/src/kibit/arithmetic.clj new file mode 100644 index 0000000..bfed168 --- /dev/null +++ b/src/kibit/arithmetic.clj @@ -0,0 +1,21 @@ +(ns kibit.arithmetic) + +(def rules + '{(+ ?x 1) inc + (+ 1 ?x) inc + (- ?x 1) dec + + (= 0 ?x) zero? + (= ?x 0) zero? + (== 0 ?x) zero? + (== ?x 0) zero? + + (< 0 ?x) pos? + (> ?x 0) pos? + (<= 1 ?x) pos? + + (< ?x 0) neg?}) + + + + \ No newline at end of file diff --git a/src/kibit/control_structures.clj b/src/kibit/control_structures.clj new file mode 100644 index 0000000..bd375a5 --- /dev/null +++ b/src/kibit/control_structures.clj @@ -0,0 +1,7 @@ +(ns kibit.control-structures) + +(def rules + '{(if ?x ?y nil) when + (if ?x nil ?y) when-not + (if (not ?x) ?y ?z) if-not}) + \ No newline at end of file diff --git a/src/kibit/core.clj b/src/kibit/core.clj new file mode 100644 index 0000000..1b4452a --- /dev/null +++ b/src/kibit/core.clj @@ -0,0 +1,48 @@ +(ns kibit.core + (:require [clojure.core.logic :as l] + [clojure.java.io :as io] + [clojure.string :as string] + [kibit.arithmetic :as arith] + [kibit.control-structures :as control] + [kibit.misc :as misc]) + (:import [java.io PushbackReader])) + +(def all-rules (merge control/rules + arith/rules + misc/rules)) + +(defn src [path] + (if-let [res (io/resource path)] + (PushbackReader. (io/reader res)) + (throw (RuntimeException. (str "File not found: " path))))) + +(defn source-file [ns-sym] + (-> (name ns-sym) + (string/replace "." "/") + (string/replace "-" "_") + (str ".clj"))) + +(defn read-ns [r] + (lazy-seq + (let [form (read r false ::eof)] + (when-not (= form ::eof) + (cons form (read-ns r)))))) + +(defn check [expr] + (doseq [[rule alt] all-rules] + (when (and (sequential? expr) + (l/unifier expr rule)) + (println "[Kibit] Consider" alt "instead of" expr)))) + +(defn expr-seq [expr] + (tree-seq sequential? + seq + expr)) + +(defn check-ns + ([ns-sym rules] + (with-open [reader (-> ns-sym source-file src)] + (doseq [form (mapcat expr-seq (read-ns reader))] + (check form)))) + ([ns-sym] + (check-ns ns-sym all-rules))) diff --git a/src/kibit/misc.clj b/src/kibit/misc.clj new file mode 100644 index 0000000..ddd6815 --- /dev/null +++ b/src/kibit/misc.clj @@ -0,0 +1,5 @@ +(ns kibit.misc) + +(def rules + '{(apply str (interpose ?x ?y)) clojure.string/join + (apply concat (apply map ?x ?y)) mapcat}) \ No newline at end of file diff --git a/src/leiningen/kibit.clj b/src/leiningen/kibit.clj new file mode 100644 index 0000000..d80effa --- /dev/null +++ b/src/leiningen/kibit.clj @@ -0,0 +1,16 @@ +(ns leiningen.kibit + (:require [clojure.tools.namespace :as ns] + [clojure.java.io :as io] + [kibit.core :as kibit])) + +(defn kibit [project] + (let [namespaces (-> project + :source-path + io/file + ns/find-namespaces-in-dir)] + (doseq [ns-sym namespaces] + (try + (println "==" ns-sym "==") + (kibit/check-ns ns-sym) + (catch RuntimeException e (println ns-sym "not found."))) + (println "done.")))) diff --git a/test/kibit/test/core.clj b/test/kibit/test/core.clj new file mode 100644 index 0000000..10472b7 --- /dev/null +++ b/test/kibit/test/core.clj @@ -0,0 +1,6 @@ +(ns kibit.test.core + (:use [kibit.core]) + (:use [clojure.test])) + +(deftest replace-me ;; FIXME: write + (is false "No tests have been written."))