diff --git a/README.md b/README.md index c03b38c..9f88ff7 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,24 @@ I am just getting started so I implemented only a few queries for now: * _st=singleton type_: find if a type implements the singleton pattern and distinguish between the three types (public field, static factory, singleton enum) * _u=utils classes_: find classes having only static methods and verify they have exactly one private constructor taking no parameters +Effective Java (the book) items implemented +=========================================== + +| Item | Status | +| ------ |---------| +| Item 1 | Done | +| item 2 | TODO | +| item 3 | Done | +| item 4 | Done | +| item 5 | TODO | +| item 6 | TODO | +| item 7 | Done | +| item 8 | Planned for v0.2 | +| item 9 | Planned for v0.2 | +| item 10 | Done | +| item 11 | TODO | +| ...item 78 | TODO | + Dev info ======== The project is written in Clojure using a java library called [JavaParser](https://github.com/javaparser/javaparser). @@ -126,4 +144,4 @@ Hope you enjoy this small project of mine. Feel free to open issues and ask ques Contributors ============ -Thanks to [David Ortiz](https://github.com/davidor) who fixed bugs, set up Travis and it is contributing many other improvements. +[David Ortiz](https://github.com/davidor) is a regular contributor: he started fixing bugs, setting up Travis and it is contributing many other improvements. diff --git a/project.clj b/project.clj index e5213a3..ce647ac 100644 --- a/project.clj +++ b/project.clj @@ -1,11 +1,12 @@ (defproject effectivejava "0.2.0-SNAPSHOT" :description "A Java linter and a tool for running queries on your Java codebase" :dependencies [[org.clojure/clojure "1.6.0"] - [com.github.javaparser/javaparser-core "2.0.0"] + [com.github.javaparser/javaparser-core "2.1.0"] [org.clojure/tools.cli "0.3.1"] [instaparse "1.3.6"] [org.javassist/javassist "3.19.0-GA"] - [org.clojars.runa/conjure "2.1.3"]] + [org.clojars.runa/conjure "2.1.3"] + [potemkin "0.3.13"]] :resource-paths ["test-resources"] :plugins [[lein-cljfmt "0.1.10"] [lein-ancient "0.6.7"] [lein-kibit "0.1.2"] [jonase/eastwood "0.2.1"] [lein-cloverage "1.0.3"]] diff --git a/src/app/interactive.clj b/src/app/interactive.clj deleted file mode 100644 index 65f04e2..0000000 --- a/src/app/interactive.clj +++ /dev/null @@ -1,119 +0,0 @@ -(ns app.interactive - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) - (:use [app.operations]) - (:use [app.itemsOnLifecycle]) - (:use [app.utils]) - (:require [instaparse.core :as insta]) - (:import [app.operations Operation])) - -; ============================================ -; Interactive mode -; ============================================ - -(def commands-grammar - (str - " = HELP | EXIT | LOAD | LIST | MC | MCP | F | ST \n" - "HELP = 'help' | 'h' \n" - "EXIT = 'exit' | 'quit' | 'q' \n" - "LOAD = 'load' STR \n" - "LIST = 'list' \n" - "MC = ('mc'|'many-constructors') 'th' NUM \n" - "MCP = ('mcp'|'many-costructor-params') 'th' NUM \n" - "F = ('f'|'finalizers') \n" - "ST = ('st'|'singletons') \n" - "WS = #'[\t ]+' \n" - "NUM = #'[0-9]+' \n" - "STR = #'\"[^\"]*\"' \n")) - -(def command-parser - (insta/parser commands-grammar)) - -(def no-classes-loaded-error - "No classes loaded. Use first") - -(declare interactive) - -(defn- exit [] - (println "Exit...")) - -(defn- list-loaded-classes [state] - (let [loadedCus (:cus state)] - (if loadedCus - (do - (println "Listing types currently loaded:") - (doseq [cu (:cus state)] - (doseq [t (.getTypes cu)] - (println " *" (getQName t))))) - (println no-classes-loaded-error)) - (interactive state))) - -(defn- help [state] - (println "h/help : print this help message") - (println "q/quit/exit : close the shell") - (println "list : list classes loaded") - (println "load DIR : load classes from DIR") - (println "mc/many-constructors th NUM : list classes with NUM or more constructors") - (println "mcp/many-constructor-params th NUM : list constructors with NUM or more parameters") - (println "f/finalizers : list classes that use finalizers") - (println "st/singletons : list singletons") - (interactive state)) - -(defn- load-classes [ast] - (let [dirnameWithApex (nth (nth (first ast) 2) 1) - dirname (subs dirnameWithApex 1 (dec (count dirnameWithApex)))] - (println "Loading" dirname) - (let [loadedCus (cus dirname)] - (println "Java files loaded:" (count loadedCus)) - (interactive {:cus loadedCus})))) - -(defn- mc-operation [state threshold] - (printOperation classesWithManyConstructorsOp (:cus state) threshold) - (interactive state)) - -(defn- mcp-operation [state threshold] - (printOperation constructorsWithManyParametersOp (:cus state) threshold) - (interactive state)) - -(defn- f-operation [state] - (printOperation finalizersOp (:cus state) nil) - (interactive state)) - -(defn- st-operation [state] - (printOperation classesAndSingletonTypeOp (:cus state) nil) - (interactive state)) - -(defn- command-not-implemented [command state] - (println "Command not implemented: " command) - (interactive state)) - -(defn- process [state input] - (let - [ast (command-parser input)] - (if - (insta/failure? ast) - (do - (println "ERROR: " ast) - (interactive state)) - (let [command (ffirst ast)] - (case command - :EXIT (exit) - :LIST (list-loaded-classes state) - :HELP (help state) - :LOAD (load-classes ast) - :MC (let [threshold (read-string (last (last (first ast))))] - (mc-operation state threshold)) - :MCP (let [threshold (read-string (last (last (first ast))))] - (mcp-operation state threshold)) - :F (f-operation state) - :ST (st-operation state) - (command-not-implemented command state)))))) - -(defn interactive [state] - (print "> ") - (flush) - (let [user-input (read-line)] - (if (empty? user-input) - (interactive state) - (process state user-input)))) diff --git a/src/app/symbol_solver/funcs.clj b/src/app/symbol_solver/funcs.clj deleted file mode 100644 index 01ad709..0000000 --- a/src/app/symbol_solver/funcs.clj +++ /dev/null @@ -1,17 +0,0 @@ -(ns app.symbol_solver.funcs - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) - (:use [app.utils]) - (:use [app.symbol_solver.scope])) - -(defn solveNameExpr - "given an instance of com.github.javaparser.ast.expr.NameExpr returns the declaration it refers to, - if it can be found, nil otherwise" - [nameExpr] - (let [name (.getName nameExpr)] - (solveSymbol nameExpr nil name))) - -(defn solveImportStmt [importStmt] - (let [name (importQName importStmt)] - (solveClass (getCu importStmt) nil name))) \ No newline at end of file diff --git a/src/app/cli.clj b/src/effectivejava/cli.clj similarity index 76% rename from src/app/cli.clj rename to src/effectivejava/cli.clj index 537343b..177da14 100644 --- a/src/app/cli.clj +++ b/src/effectivejava/cli.clj @@ -1,12 +1,12 @@ -(ns app.cli - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) - (:use [app.operations]) - (:use [app.itemsOnLifecycle]) - (:use [app.utils]) +(ns effectivejava.cli + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.navigation]) + (:use [effectivejava.operations]) + (:use [effectivejava.itemsOnLifecycle]) + (:use [effectivejava.utils]) (:require [instaparse.core :as insta]) - (:import [app.operations Operation])) + (:import [effectivejava.operations Operation])) ; ============================================ ; CLI @@ -16,7 +16,7 @@ ["-i" "--interactive" "launch interactive mode" :flag true :default false] ["-l" "--linter" "launch linter mode" :flag true :default false] ["-d" "--dir DIRNAME" "directory containing the code to check (default current dir)" :default "."] - ["-q" "--query QUERYNAME" "REQUIRED: Query to perform: mc=many constructors, mcp=many constructor parameters, st=singleton type, u=utils classes"] + ["-q" "--query QUERYNAME" "REQUIRED: Query to perform: mc=many constructors, mcp=many constructor parameters, st=singleton type, ts=overrides toString()?, u=utils classes"] ["-t" "--threshold VALUE" "Threshold to be used in the query" :default 0 :parse-fn #(Integer/parseInt %) :validate [#(>= % 0) "Must be a number equal or greater to 0"]]]) @@ -26,6 +26,7 @@ :mc classesWithManyConstructorsOp :mcp constructorsWithManyParametersOp :st classesAndSingletonTypeOp + :ts toStringOp :u utilsClassesOp}) (defn run [opts] diff --git a/src/app/core.clj b/src/effectivejava/core.clj similarity index 94% rename from src/app/core.clj rename to src/effectivejava/core.clj index 71eca23..65db0c3 100644 --- a/src/app/core.clj +++ b/src/effectivejava/core.clj @@ -1,8 +1,8 @@ -(ns app.core +(ns effectivejava.core (:require [clojure.tools.cli :refer [parse-opts]]) - (:use [app.linter]) - (:use [app.interactive]) - (:use [app.cli]) + (:use [effectivejava.linter]) + (:use [effectivejava.interactive]) + (:use [effectivejava.cli]) (:gen-class :main true)) (def self-exclusive-modes-error diff --git a/src/app/find.clj b/src/effectivejava/find.clj similarity index 86% rename from src/app/find.clj rename to src/effectivejava/find.clj index 5f6fa43..ffec524 100644 --- a/src/app/find.clj +++ b/src/effectivejava/find.clj @@ -1,15 +1,15 @@ -(ns app.find ^{:author "Federico Tomassetti" +(ns effectivejava.find ^{:author "Federico Tomassetti" :doc "This namespace contains methods to find elements in a collection of compilation units"} - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) - (:use [app.operations]) - (:use [app.itemsOnLifecycle]) - (:use [app.symbol_solver.type_solver]) - (:use [app.symbol_solver.scope]) - (:use [app.utils]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.navigation]) + (:use [effectivejava.operations]) + (:use [effectivejava.itemsOnLifecycle]) + (:use [effectivejava.symbol_solver.type_solver]) + (:use [effectivejava.symbol_solver.scope]) + (:use [effectivejava.utils]) (:require [instaparse.core :as insta]) - (:import [app.operations Operation])) + (:import [effectivejava.operations Operation])) (defn type-exact-match? [type-ref1 type-ref2] (and diff --git a/src/effectivejava/interactive.clj b/src/effectivejava/interactive.clj new file mode 100644 index 0000000..9786a84 --- /dev/null +++ b/src/effectivejava/interactive.clj @@ -0,0 +1,126 @@ +(ns effectivejava.interactive + (:use [effectivejava.model.protocols] + [effectivejava.model.javaparser] + [effectivejava.javaparser.navigation] + [effectivejava.operations] + [effectivejava.itemsOnLifecycle] + [effectivejava.utils]) + (:require [instaparse.core :as insta]) + (:import [effectivejava.operations Operation])) + +; ============================================ +; Interactive mode +; ============================================ + +(def commands-grammar + (clojure.string/join + "\n" + [" = HELP | EXIT | LOAD | LIST | MC | MCP | F | ST | TS" + "HELP = 'help' | 'h'" + "EXIT = 'exit' | 'quit' | 'q'" + "LOAD = 'load' STR" + "LIST = 'list'" + "MC = ('mc'|'many-constructors') 'th' NUM" + "MCP = ('mcp'|'many-costructor-params') 'th' NUM" + "F = ('f'|'finalizers')" + "ST = ('st'|'singletons')" + "TS = ('ts'|'toString')" + "WS = #'[\t ]+'" + "NUM = #'[0-9]+'" + "STR = #'\"[^\"]*\"'"])) + +(def command-parser + (insta/parser commands-grammar)) + +(def no-classes-loaded-error + "No classes loaded. Use first") + +(def help-message + (clojure.string/join + "\n" + ["h/help : print this help message" + "q/quit/exit : close the shell" + "list : list classes loaded" + "load DIR : load classes from DIR" + "mc/many-constructors th NUM : list classes with NUM or more constructors" + "mcp/many-constructor-params th NUM : list constructors with NUM or more parameters" + "f/finalizers : list classes that use finalizers" + "st/singletons : list singletons" + "ts/toString : list classes that do not override toString()"])) + +(def exit-message + "Exit...") + +(def operation-commands + {:MC classesWithManyConstructorsOp + :MCP constructorsWithManyParametersOp + :F finalizersOp + :ST classesAndSingletonTypeOp + :TS toStringOp}) + +(declare interactive) + +(defn- exit [] + (println exit-message)) + +(defn- list-loaded-classes [state] + (let [loadedCus (:cus state)] + (if loadedCus + (do + (println "Listing types currently loaded:") + (doseq [cu (:cus state)] + (doseq [t (.getTypes cu)] + (println " *" (getQName t))))) + (println no-classes-loaded-error)) + (interactive state))) + +(defn- show-help [state] + (println help-message) + (interactive state)) + +(defn- load-classes [ast] + (let [dirnameWithApex (nth (nth (first ast) 2) 1) + dirname (subs dirnameWithApex 1 (dec (count dirnameWithApex)))] + (println "Loading" dirname) + (let [loadedCus (cus dirname)] + (println "Java files loaded:" (count loadedCus)) + (interactive {:cus loadedCus})))) + +(defn- command-not-implemented [command state] + (println "Command not implemented: " command) + (interactive state)) + +(defn- operation [op-command state & [threshold]] + (printOperation (op-command operation-commands) (:cus state) threshold) + (interactive state)) + +(defn- process [state input] + (let + [ast (command-parser input)] + (if + (insta/failure? ast) + (do + (println "ERROR: " ast) + (interactive state)) + (let [command (ffirst ast)] + (case command + :EXIT (exit) + :LIST (list-loaded-classes state) + :HELP (show-help state) + :LOAD (load-classes ast) + :MC (let [threshold (read-string (last (last (first ast))))] + (operation :MC state threshold)) + :MCP (let [threshold (read-string (last (last (first ast))))] + (operation :MCP state threshold)) + :F (operation :F state) + :ST (operation :ST state) + :TS (operation :TS state) + (command-not-implemented command state)))))) + +(defn interactive [state] + (print "> ") + (flush) + (let [user-input (read-line)] + (if (empty? user-input) + (interactive state) + (process state user-input)))) diff --git a/src/app/itemsOnLifecycle.clj b/src/effectivejava/itemsOnLifecycle.clj similarity index 72% rename from src/app/itemsOnLifecycle.clj rename to src/effectivejava/itemsOnLifecycle.clj index 8512c4e..09f45b8 100644 --- a/src/app/itemsOnLifecycle.clj +++ b/src/effectivejava/itemsOnLifecycle.clj @@ -1,10 +1,12 @@ -(ns app.itemsOnLifecycle - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.operations]) - (:use [app.utils]) - (:use [app.javaparser.navigation]) - (:import [app.operations Operation])) +(ns effectivejava.itemsOnLifecycle + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.operations]) + (:use [effectivejava.utils]) + (:use [effectivejava.javaparser.navigation]) + (:use [effectivejava.symbol_solver.funcs]) + (:use [effectivejava.symbol_solver.type_solver]) + (:import [effectivejava.operations Operation])) ; ============================================ ; ITEM 1 @@ -118,10 +120,10 @@ ; ITEM 4 ; ============================================ -(defn isUtilClass? +(defn isUtilClass? [cl] (let [ms (getMethods cl)] - (and + (and (pos? (count ms)) (every? isStatic? ms)))) @@ -190,3 +192,43 @@ classes-using-finalizers [] [:class])) + +; ============================================ +; ITEM 10 +; ============================================ + +(defn- overrides-toString? [class] + (->> (getMethods class) + (filter #(= (getName %) "toString")) + (filter #(empty? (getParameters %))) + (count) + (= 1))) + +(defn- hierarchy-overrides-toString? [type-solver-classes class] + (binding [typeSolver (typeSolverOnList type-solver-classes)] + (->> (conj (getAllSuperclasses class) class) + (some overrides-toString?) + (true?)))) + +(defn does-not-override-toString-but-should? [classes class] + (and (not (hierarchy-overrides-toString? classes class)) + (not (isUtilClass? class)) + (not (isAbstract? class)))) + +(defn classes-that-do-not-override-toString-but-should + "Item 10 of Effective Java recommends that all classes should override + toString or one of its parents in the class hierarchy should. There is + one exception: util classes, because they do not have any parameters. + This method returns all the classes that are not util classes and that + do not override toString (and neither do their parent classes)." + [params] + (let [classes (flatten (map allClasses (:cus params)))] + (map #(vec (list % nil)) + (filter #(does-not-override-toString-but-should? classes %) + classes)))) + +(def toStringOp + (Operation. + classes-that-do-not-override-toString-but-should + [] + [:class])) diff --git a/src/app/jarloading.clj b/src/effectivejava/jarloading.clj similarity index 88% rename from src/app/jarloading.clj rename to src/effectivejava/jarloading.clj index af61470..7cffc5c 100644 --- a/src/app/jarloading.clj +++ b/src/effectivejava/jarloading.clj @@ -1,9 +1,9 @@ -(ns app.jarloading - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.operations]) - (:use [app.utils]) - (:import [app.operations Operation])) +(ns effectivejava.jarloading + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.operations]) + (:use [effectivejava.utils]) + (:import [effectivejava.operations Operation])) (import java.net.URLDecoder) (import java.util.jar.JarEntry) diff --git a/src/effectivejava/javaparser/facade.clj b/src/effectivejava/javaparser/facade.clj new file mode 100644 index 0000000..f858d2a --- /dev/null +++ b/src/effectivejava/javaparser/facade.clj @@ -0,0 +1,7 @@ +(ns effectivejava.javaparser.facade + (:use [potemkin]) + (:import [effectivejava.javaparser.navigation]) + (:import [effectivejava.javaparser.parsing])) + +(import-vars [effectivejava.javaparser.navigation allTypes allClasses allInterfaces allEnums allClassesForCus cus topLevelTypes getConstructors allConstructorsForCus getMethodDeclaration getNameExprFor getImports getVariableDeclarators getBlockStmts]) +(import-vars [effectivejava.javaparser.parsing parseFile parseString parseFileByName]) \ No newline at end of file diff --git a/src/app/javaparser/navigation.clj b/src/effectivejava/javaparser/navigation.clj similarity index 97% rename from src/app/javaparser/navigation.clj rename to src/effectivejava/javaparser/navigation.clj index 3d72cdf..f164193 100644 --- a/src/app/javaparser/navigation.clj +++ b/src/effectivejava/javaparser/navigation.clj @@ -1,7 +1,7 @@ -(ns app.javaparser.navigation - (:use [app.utils]) - (:use [app.model.protocols]) - (:use [app.javaparser.parsing])) +(ns effectivejava.javaparser.navigation + (:use [effectivejava.utils]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.javaparser.parsing])) (import com.github.javaparser.JavaParser) (import com.github.javaparser.ast.CompilationUnit) diff --git a/src/app/javaparser/parsing.clj b/src/effectivejava/javaparser/parsing.clj similarity index 93% rename from src/app/javaparser/parsing.clj rename to src/effectivejava/javaparser/parsing.clj index b2eb371..6ece8c6 100644 --- a/src/app/javaparser/parsing.clj +++ b/src/effectivejava/javaparser/parsing.clj @@ -1,6 +1,6 @@ -(ns app.javaparser.parsing - (:use [app.utils]) - (:use [app.model.protocols])) +(ns effectivejava.javaparser.parsing + (:use [effectivejava.utils]) + (:use [effectivejava.model.protocols])) (import com.github.javaparser.JavaParser) (import com.github.javaparser.ast.CompilationUnit) diff --git a/src/app/linter.clj b/src/effectivejava/linter.clj similarity index 77% rename from src/app/linter.clj rename to src/effectivejava/linter.clj index f08c35e..bcce1f2 100644 --- a/src/app/linter.clj +++ b/src/effectivejava/linter.clj @@ -1,12 +1,12 @@ -(ns app.linter - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) - (:use [app.operations]) - (:use [app.itemsOnLifecycle]) - (:use [app.utils]) +(ns effectivejava.linter + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.navigation]) + (:use [effectivejava.operations]) + (:use [effectivejava.itemsOnLifecycle]) + (:use [effectivejava.utils]) (:require [instaparse.core :as insta]) - (:import [app.operations Operation])) + (:import [effectivejava.operations Operation])) ; ============================================ ; Linter @@ -27,7 +27,9 @@ (Check. utilsClassesOp {:onlyIncorrect true} "This is a utils class and it should have exactly one private constructor taking no params") (Check. finalizersOp {} - "This class calls finalize(). Finalizers are considered to be unpredictable and often dangerous.")]) + "This class calls finalize(). Finalizers are considered to be unpredictable and often dangerous.") + (Check. toStringOp {} + "This class and its superclasses do not override toString().")]) (defn replaceParamsInMessage [message result] (clojure.string/replace message "#1#" (toString (nth result 1)))) diff --git a/src/app/model/javaparser.clj b/src/effectivejava/model/javaparser.clj similarity index 95% rename from src/app/model/javaparser.clj rename to src/effectivejava/model/javaparser.clj index 46611ec..0f497e5 100644 --- a/src/app/model/javaparser.clj +++ b/src/effectivejava/model/javaparser.clj @@ -1,8 +1,9 @@ -(ns app.model.javaparser - (:use [app.utils]) - (:use [app.model.protocols]) - (:use [app.javaparser.parsing]) - (:use [app.javaparser.navigation])) +(ns effectivejava.model.javaparser + (:import [clojure.lang Named]) + (:use [effectivejava.utils]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.javaparser.parsing]) + (:use [effectivejava.javaparser.navigation])) (import com.github.javaparser.JavaParser) (import com.github.javaparser.ast.CompilationUnit) @@ -139,7 +140,9 @@ (isStatic? [this] (ModifierSet/isStatic (.getModifiers this))) (isFinal? [this] - (ModifierSet/isFinal (.getModifiers this)))) + (ModifierSet/isFinal (.getModifiers this))) + (isAbstract? [this] + (ModifierSet/isAbstract (.getModifiers this)))) (extend-protocol WithModifiers ConstructorDeclaration @@ -291,6 +294,11 @@ (getName [this] (getName (.getId (.variable this))))) +(extend-protocol Named + com.github.javaparser.ast.type.ClassOrInterfaceType + (getName [this] + (.getName this))) + ; ============================================ ; Accessing nodes ; ============================================ diff --git a/src/app/model/javassist.clj b/src/effectivejava/model/javassist.clj similarity index 79% rename from src/app/model/javassist.clj rename to src/effectivejava/model/javassist.clj index be00c9d..a4765c4 100644 --- a/src/app/model/javassist.clj +++ b/src/effectivejava/model/javassist.clj @@ -1,6 +1,6 @@ -(ns app.model.javassist - (:use [app.utils]) - (:use [app.model.protocols])) +(ns effectivejava.model.javassist + (:use [effectivejava.utils]) + (:use [effectivejava.model.protocols])) ; ============================================ ; Naming diff --git a/src/app/model/protocols.clj b/src/effectivejava/model/protocols.clj similarity index 97% rename from src/app/model/protocols.clj rename to src/effectivejava/model/protocols.clj index 4abe2a5..d1eac59 100644 --- a/src/app/model/protocols.clj +++ b/src/effectivejava/model/protocols.clj @@ -1,4 +1,4 @@ -(ns app.model.protocols) +(ns effectivejava.model.protocols) ; In the namespace we define the protocols describing Java elements. ; We will then implement these protocols both using Javaparser ASTs and Javassist elements obtained by @@ -81,7 +81,8 @@ (isPublic? [this]) (isProtected? [this]) (isStatic? [this]) - (isFinal? [this])) + (isFinal? [this]) + (isAbstract? [this])) (defn hasPackageLevelAccess? [withModifiers] (not diff --git a/src/app/model/reflection.clj b/src/effectivejava/model/reflection.clj similarity index 69% rename from src/app/model/reflection.clj rename to src/effectivejava/model/reflection.clj index dd04c3f..a4633bb 100644 --- a/src/app/model/reflection.clj +++ b/src/effectivejava/model/reflection.clj @@ -1,8 +1,8 @@ -(ns app.model.reflection - (:use [app.utils]) - (:use [app.model.protocols]) - (:use [app.javaparser.parsing]) - (:use [app.javaparser.navigation])) +(ns effectivejava.model.reflection + (:use [effectivejava.utils]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.javaparser.parsing]) + (:use [effectivejava.javaparser.navigation])) ; Needed only for system classes diff --git a/src/app/operations.clj b/src/effectivejava/operations.clj similarity index 97% rename from src/app/operations.clj rename to src/effectivejava/operations.clj index 19a3f3d..db20eb5 100644 --- a/src/app/operations.clj +++ b/src/effectivejava/operations.clj @@ -1,6 +1,6 @@ -(ns app.operations - (:use [app.model.protocols]) - (:use [app.model.javaparser])) +(ns effectivejava.operations + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser])) (import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration) (import com.github.javaparser.ast.body.ConstructorDeclaration) diff --git a/src/effectivejava/symbol_solver/funcs.clj b/src/effectivejava/symbol_solver/funcs.clj new file mode 100644 index 0000000..17c36e9 --- /dev/null +++ b/src/effectivejava/symbol_solver/funcs.clj @@ -0,0 +1,32 @@ +(ns effectivejava.symbol_solver.funcs + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.navigation]) + (:use [effectivejava.utils]) + (:use [effectivejava.symbol_solver.scope])) + +(defn solveNameExpr + "given an instance of com.github.javaparser.ast.expr.NameExpr returns the declaration it refers to, + if it can be found, nil otherwise" + [nameExpr] + (let [name (.getName nameExpr)] + (solveSymbol nameExpr nil name))) + +(defn solveImportStmt [importStmt] + (let [name (importQName importStmt)] + (solveClass (getCu importStmt) nil name))) + +(defn solveSuperclass + "Return the definition of the superclass if the class has a superclass and if can be solved" + [classDecl] + (let [superclass (first (.getExtends classDecl))] + (when superclass + (solveClass classDecl nil (getName superclass))))) + +(defn getAllSuperclasses + "Get all the superclasses of the given class declaration (recursively)" + [classDecl] + (let [directSuperclass (solveSuperclass classDecl)] + (if directSuperclass + (into [directSuperclass] (getAllSuperclasses directSuperclass)) + []))) \ No newline at end of file diff --git a/src/app/symbol_solver/scope.clj b/src/effectivejava/symbol_solver/scope.clj similarity index 92% rename from src/app/symbol_solver/scope.clj rename to src/effectivejava/symbol_solver/scope.clj index cb89923..cceea5d 100644 --- a/src/app/symbol_solver/scope.clj +++ b/src/effectivejava/symbol_solver/scope.clj @@ -1,13 +1,12 @@ -(ns app.symbol_solver.scope - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) - (:use [app.operations]) - (:use [app.itemsOnLifecycle]) - (:use [app.utils]) - (:use [app.symbol_solver.type_solver]) +(ns effectivejava.symbol_solver.scope + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.navigation]) + (:use [effectivejava.operations]) + (:use [effectivejava.utils]) + (:use [effectivejava.symbol_solver.type_solver]) (:require [instaparse.core :as insta]) - (:import [app.operations Operation])) + (:import [effectivejava.operations Operation])) (import com.github.javaparser.JavaParser) (import com.github.javaparser.ast.CompilationUnit) @@ -64,28 +63,28 @@ ; scope: we define which declarations are visible ; ================================================ -(defprotocol scope +(defprotocol Scope ; for example in a BlockStmt containing statements [a b c d e], when solving symbols in the context of c ; it will contains only statements preceeding it [a b] (solveSymbol [this context nameToSolve]) ; solveClass solve on a subset of the elements of solveSymbol (solveClass [this context nameToSolve])) -(extend-protocol scope +(extend-protocol Scope nil (solveClass [this context nameToSolve] (typeSolver nameToSolve))) (defn declare-symbol? [symbol-name symbols-declarator] (get (declared-symbols symbols-declarator) symbol-name)) -(extend-protocol scope +(extend-protocol Scope com.github.javaparser.ast.Node (solveSymbol [this context nameToSolve] (solveSymbol (.getParentNode this) this nameToSolve)) (solveClass [this context nameToSolve] (solveClass (.getParentNode this) this nameToSolve))) -(extend-protocol scope +(extend-protocol Scope BlockStmt (solveSymbol [this context nameToSolve] (let [elementsToConsider (if (nil? context) (.getStmts this) (preceedingChildren (.getStmts this) context)) @@ -94,8 +93,8 @@ (defn solveClassInPackage [pakage nameToSolve] {:pre [typeSolver]} - ; TODO first look into the package - (typeSolver nameToSolve)) + (let [qualified-name (if pakage (str (packageName pakage) "." nameToSolve) nameToSolve)] + (typeSolver qualified-name))) (defn solveAmongVariableDeclarator [nameToSolve variableDeclarator] @@ -118,7 +117,7 @@ solvedSymbols' (remove nil? solvedSymbols)] (first solvedSymbols'))) -(extend-protocol scope +(extend-protocol Scope com.github.javaparser.ast.body.ClassOrInterfaceDeclaration (solveSymbol [this context nameToSolve] (let [amongDeclaredFields (solveAmongDeclaredFields this nameToSolve)] @@ -140,7 +139,7 @@ matchingParameters (filter (fn [p] (= nameToSolve (.getName (.getId p)))) parameters)] (first matchingParameters))) -(extend-protocol scope +(extend-protocol Scope com.github.javaparser.ast.body.MethodDeclaration (solveSymbol [this context nameToSolve] (or (solve-among-parameters this nameToSolve) @@ -166,7 +165,7 @@ correspondingClasses (map typeSolver importNames)] (first correspondingClasses))) -(extend-protocol scope +(extend-protocol Scope com.github.javaparser.ast.CompilationUnit ; TODO consider imports (solveClass [this context nameToSolve] diff --git a/src/app/symbol_solver/type_solver.clj b/src/effectivejava/symbol_solver/type_solver.clj similarity index 87% rename from src/app/symbol_solver/type_solver.clj rename to src/effectivejava/symbol_solver/type_solver.clj index 6ae6b6c..aa6cffe 100644 --- a/src/app/symbol_solver/type_solver.clj +++ b/src/effectivejava/symbol_solver/type_solver.clj @@ -1,12 +1,11 @@ -(ns app.symbol_solver.type_solver - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.operations]) - (:use [app.itemsOnLifecycle]) - (:use [app.jarloading]) - (:use [app.utils]) +(ns effectivejava.symbol_solver.type_solver + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.operations]) + (:use [effectivejava.jarloading]) + (:use [effectivejava.utils]) (:require [instaparse.core :as insta]) - (:import [app.operations Operation])) + (:import [effectivejava.operations Operation])) ; A TypeSolver is a function which, given a name, return a TypeRef or nil (if not found) diff --git a/src/app/utils.clj b/src/effectivejava/utils.clj similarity index 97% rename from src/app/utils.clj rename to src/effectivejava/utils.clj index b282d7b..c7f07a7 100644 --- a/src/app/utils.clj +++ b/src/effectivejava/utils.clj @@ -1,4 +1,4 @@ -(ns app.utils) +(ns effectivejava.utils) (def not-nil? (complement nil?)) diff --git a/test-resources/sample-codebases/samples/Superclasses.java b/test-resources/sample-codebases/samples/Superclasses.java new file mode 100644 index 0000000..7e48a08 --- /dev/null +++ b/test-resources/sample-codebases/samples/Superclasses.java @@ -0,0 +1,13 @@ +package sample; + +class SC_C { + +} + +class SC_B extends SC_C { + +} + +public class SC_A extends SC_B { + +} \ No newline at end of file diff --git a/test-resources/sample-codebases/samples/test_item10/AbstractClassDoesNotOverrideToString.java b/test-resources/sample-codebases/samples/test_item10/AbstractClassDoesNotOverrideToString.java new file mode 100644 index 0000000..10dd633 --- /dev/null +++ b/test-resources/sample-codebases/samples/test_item10/AbstractClassDoesNotOverrideToString.java @@ -0,0 +1,7 @@ +package effectivejava.test.samples; + +public abstract class AbstractClassDoesNotOverrideToString { + + abstract void aMethod(); + +} \ No newline at end of file diff --git a/test-resources/sample-codebases/samples/test_item10/ClassThatDeclaresToStringWithParams.java b/test-resources/sample-codebases/samples/test_item10/ClassThatDeclaresToStringWithParams.java new file mode 100644 index 0000000..be2c9f0 --- /dev/null +++ b/test-resources/sample-codebases/samples/test_item10/ClassThatDeclaresToStringWithParams.java @@ -0,0 +1,19 @@ +package effectivejava.test.samples; + +public class ClassThatDeclaresToStringWithParams { + + private final int a; + + public ClassThatDeclaresToStringWithParams(int a) { + this.a = a; + } + + public int getA() { + return a; + } + + public String toString(int b) { + return Integer.toString(b); + } + +} diff --git a/test-resources/sample-codebases/samples/test_item10/ClassThatDoesNotOverrideToString.java b/test-resources/sample-codebases/samples/test_item10/ClassThatDoesNotOverrideToString.java new file mode 100644 index 0000000..d912600 --- /dev/null +++ b/test-resources/sample-codebases/samples/test_item10/ClassThatDoesNotOverrideToString.java @@ -0,0 +1,15 @@ +package effectivejava.test.samples; + +public class ClassThatDoesNotOverrideToString { + + private final int a; + + public ClassThatDoesNotOverrideToString(int a) { + this.a = a; + } + + public int getA() { + return a; + } + +} diff --git a/test-resources/sample-codebases/samples/test_item10/ClassThatOverridesToString.java b/test-resources/sample-codebases/samples/test_item10/ClassThatOverridesToString.java new file mode 100644 index 0000000..c604560 --- /dev/null +++ b/test-resources/sample-codebases/samples/test_item10/ClassThatOverridesToString.java @@ -0,0 +1,22 @@ +package effectivejava.test.samples; + +public class ClassThatOverridesToString { + + private final int a; + + public ClassThatOverridesToString(int a) { + this.a = a; + } + + public int getA() { + return a; + } + + @Override + public String toString() { + return "effectivejava.test.samples.ClassThatOverridesToString{" + + "a=" + a + + '}'; + } + +} diff --git a/test-resources/sample-codebases/samples/test_item10/ClassWhoseParentOverridesToString.java b/test-resources/sample-codebases/samples/test_item10/ClassWhoseParentOverridesToString.java new file mode 100644 index 0000000..2ca05f9 --- /dev/null +++ b/test-resources/sample-codebases/samples/test_item10/ClassWhoseParentOverridesToString.java @@ -0,0 +1,16 @@ +package effectivejava.test.samples; + +public class ClassWhoseParentOverridesToString extends ParentClassThatOverridesToString { + + private final int a; + + public ClassWhoseParentOverridesToString (int a, int b) { + super(b); + this.a = a; + } + + public int getA() { + return a; + } + +} diff --git a/test-resources/sample-codebases/samples/test_item10/ParentClassThatOverridesToString.java b/test-resources/sample-codebases/samples/test_item10/ParentClassThatOverridesToString.java new file mode 100644 index 0000000..d1d28d2 --- /dev/null +++ b/test-resources/sample-codebases/samples/test_item10/ParentClassThatOverridesToString.java @@ -0,0 +1,22 @@ +package effectivejava.test.samples; + +public class ParentClassThatOverridesToString { + + private final int b; + + public ParentClassThatOverridesToString(int b) { + this.b = b; + } + + public int getB() { + return b; + } + + @Override + public String toString() { + return "effectivejava.test.samples.ParentClassThatOverridesToString{" + + "b=" + b + + '}'; + } + +} diff --git a/test-resources/sample-codebases/samples/test_item10/UtilsClassDoesNotOverrideToString.java b/test-resources/sample-codebases/samples/test_item10/UtilsClassDoesNotOverrideToString.java new file mode 100644 index 0000000..e816625 --- /dev/null +++ b/test-resources/sample-codebases/samples/test_item10/UtilsClassDoesNotOverrideToString.java @@ -0,0 +1,9 @@ +package effectivejava.test.samples; + +public class UtilsClassDoesNotOverrideToString { + + public static int performSomeOperation(int a) { + return a + 5; + } + +} \ No newline at end of file diff --git a/test/app/test/interactive.clj b/test/app/test/interactive.clj deleted file mode 100644 index 39f9451..0000000 --- a/test/app/test/interactive.clj +++ /dev/null @@ -1,24 +0,0 @@ -(ns app.test.interactive - (:use [app.interactive] - [conjure.core] - [clojure.test])) - -(deftest can-quit - (let [input-command "quit\r"] - (with-in-str - input-command - (mocking [println print flush] - (interactive []) - (verify-call-times-for println 1) - (verify-first-call-args-for println "Exit..."))))) - -(deftest list-shows-error-if-no-classes-loaded - (let [command-sequence ["list" "quit"] - input-string (clojure.string/join "\r" command-sequence)] - (with-in-str - input-string - (mocking [println print flush] - (interactive []) - (verify-call-times-for println 2) - (verify-first-call-args-for println - no-classes-loaded-error))))) diff --git a/test/app/test/symbol_solver/helper.clj b/test/app/test/symbol_solver/helper.clj deleted file mode 100644 index 858ac3b..0000000 --- a/test/app/test/symbol_solver/helper.clj +++ /dev/null @@ -1,18 +0,0 @@ -(ns app.test.symbol_solver.helper - (:use [app.jarloading]) - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) - (:use [app.symbol_solver.funcs]) - (:use [app.symbol_solver.type_solver]) - (:use [app.symbol_solver.scope]) - (:use [app.utils]) - (:use [clojure.test])) - -(def javaparser2 "test-resources/sample-jars/javaparser-core-2.0.0.jar") -(def samplesCus (cus "test-resources/sample-codebases/samples/")) -(def sampleClasses (flatten (map allTypes samplesCus))) - -(defn sampleClass [name] - {:post [%]} - (first (filter (fn [c] (= name (.getName c))) sampleClasses))) diff --git a/test/app/test/acceptance.clj b/test/effectivejava/test/acceptance.clj similarity index 92% rename from test/app/test/acceptance.clj rename to test/effectivejava/test/acceptance.clj index 5d533ee..70a08ed 100644 --- a/test/app/test/acceptance.clj +++ b/test/effectivejava/test/acceptance.clj @@ -1,9 +1,9 @@ -(ns app.test.acceptance - (:use [app.model.javaparser]) - (:use [app.operations]) - (:use [app.itemsOnLifecycle]) - (:use [app.javaparser.parsing]) - (:use [app.javaparser.navigation]) +(ns effectivejava.test.acceptance + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.operations]) + (:use [effectivejava.itemsOnLifecycle]) + (:use [effectivejava.javaparser.parsing]) + (:use [effectivejava.javaparser.navigation]) (:use [clojure.test])) (def javaparserCus (cus "test-resources/sample-codebases/javaparser/")) diff --git a/test/app/test/cli.clj b/test/effectivejava/test/cli.clj similarity index 86% rename from test/app/test/cli.clj rename to test/effectivejava/test/cli.clj index 76d25fb..0e0deda 100644 --- a/test/app/test/cli.clj +++ b/test/effectivejava/test/cli.clj @@ -1,13 +1,13 @@ -(ns app.test.cli +(ns effectivejava.test.cli (:require [clojure.tools.cli :refer [parse-opts]]) (:use [conjure.core]) - (:use [app.cli]) - (:use [app.core]) - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.itemsOnLifecycle]) - (:use [app.javaparser.navigation]) - (:use [app.operations]) + (:use [effectivejava.cli]) + (:use [effectivejava.core]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.itemsOnLifecycle]) + (:use [effectivejava.javaparser.navigation]) + (:use [effectivejava.operations]) (:use [clojure.test])) (def javaparserCus (cus "test-resources/sample-codebases/javaparser/")) @@ -127,10 +127,10 @@ (deftest run-invoke-the-right-stuff (let [opts {:dir "mydir", :threshold 43, :query :mcp}] (stubbing [cus '(:cu1 :cu2 :cu3)] - (mocking [println printOperation] - (run opts) - (verify-call-times-for println 1) - (verify-call-times-for printOperation 1) - (verify-first-call-args-for println "Considering" 3 "Java files") - (verify-first-call-args-for printOperation constructorsWithManyParametersOp '(:cu1 :cu2 :cu3) 43))))) + (mocking [println printOperation] + (run opts) + (verify-call-times-for println 1) + (verify-call-times-for printOperation 1) + (verify-first-call-args-for println "Considering" 3 "Java files") + (verify-first-call-args-for printOperation constructorsWithManyParametersOp '(:cu1 :cu2 :cu3) 43))))) diff --git a/test/app/test/core.clj b/test/effectivejava/test/core.clj similarity index 60% rename from test/app/test/core.clj rename to test/effectivejava/test/core.clj index b4f5474..8a60a08 100644 --- a/test/app/test/core.clj +++ b/test/effectivejava/test/core.clj @@ -1,14 +1,19 @@ -(ns app.test.core - (:use [app.core]) - (:use [app.test.helper]) - (:use [app.itemsOnLifecycle]) - (:use [app.interactive]) +(ns effectivejava.test.core + (:use [effectivejava.core]) + (:use [effectivejava.test.helper]) + (:use [effectivejava.itemsOnLifecycle]) + (:use [effectivejava.interactive]) + (:use [effectivejava.javaparser.facade]) (:use [clojure.test]) (:use [conjure.core]) + (:use [effectivejava.symbol_solver.funcs]) + (:use [effectivejava.symbol_solver.type_solver]) (:require [instaparse.core :as insta])) (load "helper") +(def sampleClassesItem10Test (cus "test-resources/sample-codebases/samples/test_item10")) + ; ============================================ ; usageError ; ============================================ @@ -93,7 +98,7 @@ (verify-first-call-args-for exit-error!))) ; ============================================ -; Other FIXME organize! +; Item 3 - Singletons ; ============================================ (deftest testIsPublicFieldSingletonPositive @@ -132,6 +137,10 @@ (let [cl (parseType "NotSingletonEnum_NotOnlyInstance")] (is (not (isSingletonEnum? cl))))) +; ============================================ +; Item 7 - Avoid finalizers +; ============================================ + (deftest testClassCallsFinalizer (let [cl (parseType "ClassWithFinalizers")] (is (true? (calls-finalizers? cl))))) @@ -148,9 +157,74 @@ (let [cl (parseType "ClassWithCallToFinalizeWithParams")] (is (false? (calls-finalizers? cl))))) -; ============================================================= +; ============================================ +; Item 10 - Override toString() +; ============================================ + +(deftest testClassThatOverridesToString + (let [type-solver-classes (flatten (map allTypes sampleClassesItem10Test)) + cl (first (filter #(= (.getName %) "ClassThatOverridesToString") + type-solver-classes))] + (is (false? (does-not-override-toString-but-should? + type-solver-classes cl))))) + +(deftest testClassWhoseParentsOverrideToString + (let [type-solver-classes (flatten (map allTypes sampleClassesItem10Test)) + cl (first (filter #(= (.getName %) "ClassWhoseParentOverridesToString") + type-solver-classes))] + (is (false? (does-not-override-toString-but-should? + type-solver-classes cl))))) + +(deftest testClassThatDeclaresToStringWithParams + (let [type-solver-classes (flatten (map allTypes sampleClassesItem10Test)) + cl (first (filter + #(= (.getName %) "ClassThatDeclaresToStringWithParams") + type-solver-classes))] + (is (does-not-override-toString-but-should? type-solver-classes cl)))) + +(deftest testClassThatDoesNotOverrideToString + (let [type-solver-classes (flatten (map allTypes sampleClassesItem10Test)) + cl (first (filter #(= (.getName %) "ClassThatDoesNotOverrideToString") + type-solver-classes))] + (is (does-not-override-toString-but-should? type-solver-classes cl)))) + +(deftest testUtilClassThatDoesNotOverrideToString + (let [type-solver-classes (flatten (map allTypes sampleClassesItem10Test)) + cl (first (filter #(= (.getName %) "UtilsClassDoesNotOverrideToString") + type-solver-classes))] + (is (false? (does-not-override-toString-but-should? + type-solver-classes cl))))) + +(deftest abstractClassDoesNotNeedToOverrideToString + (let [type-solver-classes (flatten (map allTypes sampleClassesItem10Test)) + cl (first (filter #(= (.getName %) "AbstractClassDoesNotOverrideToString") + type-solver-classes))] + (is (false? (does-not-override-toString-but-should? + type-solver-classes cl))))) + +; ============================================ +; Type solver gets superclasses correctly +; ============================================ + +(deftest test-getAllSuperclasses-depth-0 + (let [type-solver-classes (flatten (map allTypes sampleClassesItem10Test)) + cl (first (filter #(= (.getName %) "ParentClassThatOverridesToString") + type-solver-classes))] + (binding [typeSolver (typeSolverOnList type-solver-classes)] + (let [superclasses (getAllSuperclasses cl)] + (is (= 0 (count superclasses))))))) + +(deftest test-getAllSuperclasses-depth-1 + (let [type-solver-classes (flatten (map allTypes sampleClassesItem10Test)) + cl (first (filter #(= (.getName %) "ClassWhoseParentOverridesToString") + type-solver-classes))] + (binding [typeSolver (typeSolverOnList type-solver-classes)] + (let [superclasses (getAllSuperclasses cl)] + (is (= 1 (count superclasses))))))) + +; ============================================ ; Command parser -; ============================================================= +; ============================================ (deftest testUnknown (is (insta/failure? (command-parser "a not valid command")))) diff --git a/test/app/test/find_test.clj b/test/effectivejava/test/find_test.clj similarity index 90% rename from test/app/test/find_test.clj rename to test/effectivejava/test/find_test.clj index daea5c3..408a31a 100644 --- a/test/app/test/find_test.clj +++ b/test/effectivejava/test/find_test.clj @@ -1,13 +1,12 @@ -(ns app.test.find_test - (:use [app.core]) - (:use [app.test.helper]) - (:use [app.find]) - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.parsing]) - (:use [app.javaparser.navigation]) - (:use [app.symbol_solver.type_solver]) - (:use [app.symbol_solver.scope]) +(ns effectivejava.test.find_test + (:use [effectivejava.core]) + (:use [effectivejava.test.helper]) + (:use [effectivejava.find]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.facade]) + (:use [effectivejava.symbol_solver.type_solver]) + (:use [effectivejava.symbol_solver.scope]) (:use [clojure.test]) (:use [conjure.core]) (:require [instaparse.core :as insta])) diff --git a/test/app/test/helper.clj b/test/effectivejava/test/helper.clj similarity index 66% rename from test/app/test/helper.clj rename to test/effectivejava/test/helper.clj index 919fd9b..be4eede 100644 --- a/test/app/test/helper.clj +++ b/test/effectivejava/test/helper.clj @@ -1,10 +1,10 @@ -(ns app.test.helper - (:use [app.model.javaparser]) +(ns effectivejava.test.helper + (:use [effectivejava.model.javaparser]) (:use [clojure.test]) - (:use [app.javaparser.parsing])) + (:use [effectivejava.javaparser.facade])) (defn readResource [filename] - (let [resourceName (str "app/test/samples/" filename ".java.txt") + (let [resourceName (str "effectivejava/test/samples/" filename ".java.txt") code (slurp (clojure.java.io/resource resourceName))] code)) diff --git a/test/effectivejava/test/interactive.clj b/test/effectivejava/test/interactive.clj new file mode 100644 index 0000000..5058ae9 --- /dev/null +++ b/test/effectivejava/test/interactive.clj @@ -0,0 +1,82 @@ +(ns effectivejava.test.interactive + (:use [effectivejava.interactive] + [effectivejava.javaparser.facade] + [effectivejava.operations] + [effectivejava.itemsOnLifecycle] + [conjure.core] + [clojure.test])) + +(def javaparser-cus-path + "test-resources/sample-codebases/javaparser/") + +(defn- command-sequence->input-str [command-sequence] + (clojure.string/join "\r" command-sequence)) + +(deftest can-quit + (let [input-command "quit\r"] + (with-in-str + input-command + (mocking [println print flush] + (interactive []) + (verify-call-times-for println 1) + (verify-first-call-args-for println exit-message))))) + +(deftest shows-help + (let [command-sequence ["help" "quit"] + input-string (command-sequence->input-str command-sequence)] + (with-in-str + input-string + (mocking [println print flush] + (interactive []) + (verify-call-times-for println 2) + (verify-first-call-args-for println help-message))))) + +(deftest list-shows-error-if-no-classes-loaded + (let [command-sequence ["list" "quit"] + input-string (command-sequence->input-str command-sequence)] + (with-in-str + input-string + (mocking [println print flush] + (interactive []) + (verify-call-times-for println 2) + (verify-first-call-args-for println + no-classes-loaded-error))))) + + +;; The next four tests check that the operations that can +;; be used from the interactive mode (mc, mcp, etc.) work as expected. +;; We are interested in checking whether the printOperation function of the +;; effectivejava.operations namespace is called with the correct parameters. +;; For this reason, we can use any compilation units and any threshold (for +;; the operations that require one). + +;; All the operations can be tested in the same way. +(defn- test-operation [op-command operation & [threshold]] + (let [javaparser-cus {:cus (take 2 (cus javaparser-cus-path))} + first-command (if threshold + (str op-command " th " threshold) + (str op-command)) + command-sequence [first-command "quit"] + input-string (command-sequence->input-str command-sequence)] + (with-in-str + input-string + (mocking [println print flush printOperation] + (interactive javaparser-cus) + (verify-call-times-for printOperation 1) + (verify-first-call-args-for + printOperation operation (:cus javaparser-cus) threshold))))) + +(deftest can-execute-mc-operation + (test-operation "mc" classesWithManyConstructorsOp 2)) + +(deftest can-execute-mcp-operation + (test-operation "mcp" constructorsWithManyParametersOp 3)) + +(deftest can-execute-f-operation + (test-operation "f" finalizersOp)) + +(deftest can-execute-st-operation + (test-operation "st" classesAndSingletonTypeOp)) + +(deftest can-execute-ts-operation + (test-operation "ts" toStringOp)) diff --git a/test/app/test/itemsOnLifecycle.clj b/test/effectivejava/test/itemsOnLifecycle.clj similarity index 60% rename from test/app/test/itemsOnLifecycle.clj rename to test/effectivejava/test/itemsOnLifecycle.clj index ebf996b..06c8e88 100644 --- a/test/app/test/itemsOnLifecycle.clj +++ b/test/effectivejava/test/itemsOnLifecycle.clj @@ -1,12 +1,12 @@ -(ns app.test.itemsOnLifecycle - (:use [app.core]) - (:use [app.operations]) - (:use [app.itemsOnLifecycle]) - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) +(ns effectivejava.test.itemsOnLifecycle + (:use [effectivejava.core]) + (:use [effectivejava.operations]) + (:use [effectivejava.itemsOnLifecycle]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.facade]) (:use [clojure.test]) - (:use [app.test.helper]) + (:use [effectivejava.test.helper]) (:require [instaparse.core :as insta])) (def javaparserCus (cus "test-resources/sample-codebases/javaparser/")) diff --git a/test/app/test/jarloading.clj b/test/effectivejava/test/jarloading.clj similarity index 91% rename from test/app/test/jarloading.clj rename to test/effectivejava/test/jarloading.clj index 442661e..f0b47af 100644 --- a/test/app/test/jarloading.clj +++ b/test/effectivejava/test/jarloading.clj @@ -1,7 +1,7 @@ -(ns app.test.jarloading - (:use [app.jarloading]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) +(ns effectivejava.test.jarloading + (:use [effectivejava.jarloading]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.facade]) (:use [clojure.test])) (def javaparser2 "test-resources/sample-jars/javaparser-core-2.0.0.jar") diff --git a/test/app/test/javaparser.clj b/test/effectivejava/test/javaparser.clj similarity index 90% rename from test/app/test/javaparser.clj rename to test/effectivejava/test/javaparser.clj index 640d918..8e5695b 100644 --- a/test/app/test/javaparser.clj +++ b/test/effectivejava/test/javaparser.clj @@ -1,11 +1,11 @@ -(ns app.test.javaparser - (:use [app.core]) - (:use [app.itemsOnLifecycle]) - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) +(ns effectivejava.test.javaparser + (:use [effectivejava.core]) + (:use [effectivejava.itemsOnLifecycle]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.facade]) (:use [clojure.test]) - (:use [app.test.helper]) + (:use [effectivejava.test.helper]) (:require [instaparse.core :as insta])) (load "helper") diff --git a/test/app/test/javaparser/navigation_test.clj b/test/effectivejava/test/javaparser/navigation_test.clj similarity index 93% rename from test/app/test/javaparser/navigation_test.clj rename to test/effectivejava/test/javaparser/navigation_test.clj index fbce012..743b59b 100644 --- a/test/app/test/javaparser/navigation_test.clj +++ b/test/effectivejava/test/javaparser/navigation_test.clj @@ -1,10 +1,9 @@ -(ns app.test.javaparser.navigation_test - (:use [app.itemsOnLifecycle]) - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) - (:use [app.javaparser.parsing]) - (:use [app.test.helper]) +(ns effectivejava.test.javaparser.navigation_test + (:use [effectivejava.itemsOnLifecycle]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.facade]) + (:use [effectivejava.test.helper]) (:use [clojure.test])) ; ============================================ diff --git a/test/app/test/model/javaparser_test.clj b/test/effectivejava/test/model/javaparser_test.clj similarity index 92% rename from test/app/test/model/javaparser_test.clj rename to test/effectivejava/test/model/javaparser_test.clj index 90a4ebb..ff61351 100644 --- a/test/app/test/model/javaparser_test.clj +++ b/test/effectivejava/test/model/javaparser_test.clj @@ -1,9 +1,9 @@ -(ns app.test.model.javaparser_test - (:use [app.itemsOnLifecycle]) - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) - (:use [app.test.helper]) +(ns effectivejava.test.model.javaparser_test + (:use [effectivejava.itemsOnLifecycle]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.facade]) + (:use [effectivejava.test.helper]) (:use [clojure.test])) ; ============================================ diff --git a/test/app/test/operations.clj b/test/effectivejava/test/operations.clj similarity index 86% rename from test/app/test/operations.clj rename to test/effectivejava/test/operations.clj index 49c62c5..b62e79f 100644 --- a/test/app/test/operations.clj +++ b/test/effectivejava/test/operations.clj @@ -1,10 +1,10 @@ -(ns app.test.operations - (:use [app.core]) - (:use [app.operations]) - (:use [app.itemsOnLifecycle]) - (:use [app.model.javaparser]) +(ns effectivejava.test.operations + (:use [effectivejava.core]) + (:use [effectivejava.operations]) + (:use [effectivejava.itemsOnLifecycle]) + (:use [effectivejava.model.javaparser]) (:use [clojure.test]) - (:use [app.test.helper]) + (:use [effectivejava.test.helper]) (:require [instaparse.core :as insta])) (deftest testFormatValueNoCutOnStartNeeded diff --git a/test/app/test/samples/ASimpleClass.java.txt b/test/effectivejava/test/samples/ASimpleClass.java.txt similarity index 100% rename from test/app/test/samples/ASimpleClass.java.txt rename to test/effectivejava/test/samples/ASimpleClass.java.txt diff --git a/test/app/test/samples/ASimpleClassInAPackage.java.txt b/test/effectivejava/test/samples/ASimpleClassInAPackage.java.txt similarity index 100% rename from test/app/test/samples/ASimpleClassInAPackage.java.txt rename to test/effectivejava/test/samples/ASimpleClassInAPackage.java.txt diff --git a/test/app/test/samples/ASimpleEnum.java.txt b/test/effectivejava/test/samples/ASimpleEnum.java.txt similarity index 100% rename from test/app/test/samples/ASimpleEnum.java.txt rename to test/effectivejava/test/samples/ASimpleEnum.java.txt diff --git a/test/app/test/samples/ASimpleInterface.java.txt b/test/effectivejava/test/samples/ASimpleInterface.java.txt similarity index 100% rename from test/app/test/samples/ASimpleInterface.java.txt rename to test/effectivejava/test/samples/ASimpleInterface.java.txt diff --git a/test/app/test/samples/ASimplePackage.java.txt b/test/effectivejava/test/samples/ASimplePackage.java.txt similarity index 100% rename from test/app/test/samples/ASimplePackage.java.txt rename to test/effectivejava/test/samples/ASimplePackage.java.txt diff --git a/test/app/test/samples/ASimplePublicFinalClass.java.txt b/test/effectivejava/test/samples/ASimplePublicFinalClass.java.txt similarity index 100% rename from test/app/test/samples/ASimplePublicFinalClass.java.txt rename to test/effectivejava/test/samples/ASimplePublicFinalClass.java.txt diff --git a/test/app/test/samples/AnnidatedTypes.java.txt b/test/effectivejava/test/samples/AnnidatedTypes.java.txt similarity index 100% rename from test/app/test/samples/AnnidatedTypes.java.txt rename to test/effectivejava/test/samples/AnnidatedTypes.java.txt diff --git a/test/app/test/samples/ClassWithCallToFinalizeWithParams.java.txt b/test/effectivejava/test/samples/ClassWithCallToFinalizeWithParams.java.txt similarity index 100% rename from test/app/test/samples/ClassWithCallToFinalizeWithParams.java.txt rename to test/effectivejava/test/samples/ClassWithCallToFinalizeWithParams.java.txt diff --git a/test/app/test/samples/ClassWithCommentedCallToFinalize.java.txt b/test/effectivejava/test/samples/ClassWithCommentedCallToFinalize.java.txt similarity index 100% rename from test/app/test/samples/ClassWithCommentedCallToFinalize.java.txt rename to test/effectivejava/test/samples/ClassWithCommentedCallToFinalize.java.txt diff --git a/test/app/test/samples/ClassWithErrors.java.txt b/test/effectivejava/test/samples/ClassWithErrors.java.txt similarity index 100% rename from test/app/test/samples/ClassWithErrors.java.txt rename to test/effectivejava/test/samples/ClassWithErrors.java.txt diff --git a/test/app/test/samples/ClassWithFinalizers.java.txt b/test/effectivejava/test/samples/ClassWithFinalizers.java.txt similarity index 100% rename from test/app/test/samples/ClassWithFinalizers.java.txt rename to test/effectivejava/test/samples/ClassWithFinalizers.java.txt diff --git a/test/app/test/samples/ClassWithPublicFieldSingleton.java.txt b/test/effectivejava/test/samples/ClassWithPublicFieldSingleton.java.txt similarity index 100% rename from test/app/test/samples/ClassWithPublicFieldSingleton.java.txt rename to test/effectivejava/test/samples/ClassWithPublicFieldSingleton.java.txt diff --git a/test/app/test/samples/ClassWithPublicMethodSingleton.java.txt b/test/effectivejava/test/samples/ClassWithPublicMethodSingleton.java.txt similarity index 100% rename from test/app/test/samples/ClassWithPublicMethodSingleton.java.txt rename to test/effectivejava/test/samples/ClassWithPublicMethodSingleton.java.txt diff --git a/test/app/test/samples/ClassWithoutFinalizers.java.txt b/test/effectivejava/test/samples/ClassWithoutFinalizers.java.txt similarity index 100% rename from test/app/test/samples/ClassWithoutFinalizers.java.txt rename to test/effectivejava/test/samples/ClassWithoutFinalizers.java.txt diff --git a/test/app/test/samples/ClassWithoutPublicFieldSingleton_NotNamedInstance.java.txt b/test/effectivejava/test/samples/ClassWithoutPublicFieldSingleton_NotNamedInstance.java.txt similarity index 100% rename from test/app/test/samples/ClassWithoutPublicFieldSingleton_NotNamedInstance.java.txt rename to test/effectivejava/test/samples/ClassWithoutPublicFieldSingleton_NotNamedInstance.java.txt diff --git a/test/app/test/samples/ClassWithoutPublicFieldSingleton_NotPublic.java.txt b/test/effectivejava/test/samples/ClassWithoutPublicFieldSingleton_NotPublic.java.txt similarity index 100% rename from test/app/test/samples/ClassWithoutPublicFieldSingleton_NotPublic.java.txt rename to test/effectivejava/test/samples/ClassWithoutPublicFieldSingleton_NotPublic.java.txt diff --git a/test/app/test/samples/ClassWithoutPublicFieldSingleton_NotStatic.java.txt b/test/effectivejava/test/samples/ClassWithoutPublicFieldSingleton_NotStatic.java.txt similarity index 100% rename from test/app/test/samples/ClassWithoutPublicFieldSingleton_NotStatic.java.txt rename to test/effectivejava/test/samples/ClassWithoutPublicFieldSingleton_NotStatic.java.txt diff --git a/test/app/test/samples/ClassWithoutPublicMethodSingleton_NotNamedGetInstance.java.txt b/test/effectivejava/test/samples/ClassWithoutPublicMethodSingleton_NotNamedGetInstance.java.txt similarity index 100% rename from test/app/test/samples/ClassWithoutPublicMethodSingleton_NotNamedGetInstance.java.txt rename to test/effectivejava/test/samples/ClassWithoutPublicMethodSingleton_NotNamedGetInstance.java.txt diff --git a/test/app/test/samples/LotOfTypes.java.txt b/test/effectivejava/test/samples/LotOfTypes.java.txt similarity index 100% rename from test/app/test/samples/LotOfTypes.java.txt rename to test/effectivejava/test/samples/LotOfTypes.java.txt diff --git a/test/app/test/samples/NotSingletonEnum_NoInstance.java.txt b/test/effectivejava/test/samples/NotSingletonEnum_NoInstance.java.txt similarity index 100% rename from test/app/test/samples/NotSingletonEnum_NoInstance.java.txt rename to test/effectivejava/test/samples/NotSingletonEnum_NoInstance.java.txt diff --git a/test/app/test/samples/NotSingletonEnum_NotOnlyInstance.java.txt b/test/effectivejava/test/samples/NotSingletonEnum_NotOnlyInstance.java.txt similarity index 100% rename from test/app/test/samples/NotSingletonEnum_NotOnlyInstance.java.txt rename to test/effectivejava/test/samples/NotSingletonEnum_NotOnlyInstance.java.txt diff --git a/test/app/test/samples/RefTypes.java.txt b/test/effectivejava/test/samples/RefTypes.java.txt similarity index 100% rename from test/app/test/samples/RefTypes.java.txt rename to test/effectivejava/test/samples/RefTypes.java.txt diff --git a/test/app/test/samples/SingletonEnum.java.txt b/test/effectivejava/test/samples/SingletonEnum.java.txt similarity index 100% rename from test/app/test/samples/SingletonEnum.java.txt rename to test/effectivejava/test/samples/SingletonEnum.java.txt diff --git a/test/app/test/samples/TypesToMatch.java.txt b/test/effectivejava/test/samples/TypesToMatch.java.txt similarity index 100% rename from test/app/test/samples/TypesToMatch.java.txt rename to test/effectivejava/test/samples/TypesToMatch.java.txt diff --git a/test/app/test/symbol_solver/funcs_test.clj b/test/effectivejava/test/symbol_solver/funcs_test.clj similarity index 74% rename from test/app/test/symbol_solver/funcs_test.clj rename to test/effectivejava/test/symbol_solver/funcs_test.clj index dd33747..a9a8484 100644 --- a/test/app/test/symbol_solver/funcs_test.clj +++ b/test/effectivejava/test/symbol_solver/funcs_test.clj @@ -1,13 +1,13 @@ -(ns app.test.symbol_solver.funcs_test - (:use [app.jarloading]) - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) - (:use [app.symbol_solver.funcs]) - (:use [app.symbol_solver.type_solver]) - (:use [app.symbol_solver.scope]) - (:use [app.test.symbol_solver.helper]) - (:use [app.utils]) +(ns effectivejava.test.symbol_solver.funcs_test + (:use [effectivejava.jarloading]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.facade]) + (:use [effectivejava.symbol_solver.funcs]) + (:use [effectivejava.symbol_solver.type_solver]) + (:use [effectivejava.symbol_solver.scope]) + (:use [effectivejava.test.symbol_solver.helper]) + (:use [effectivejava.utils]) (:use [clojure.test])) ; ============================================ @@ -114,3 +114,35 @@ _ (assert importStmt) importedType (solveImportStmt importStmt)] (is importedType)))) + +; ============================================ +; solveSuperclass +; ============================================ + +(deftest testSolveSuperclassSimpleCase + (binding [typeSolver (typeSolverOnJar javaparser2)] + (let [scA (sampleClass "SC_A") + _ (assert scA) + scB (sampleClass "SC_B") + _ (assert scB) + scC (sampleClass "SC_C") + _ (assert scC)] + (is (= scB (solveSuperclass scA))) + (is (= scC (solveSuperclass scB))) + (is (nil? (solveSuperclass scC)))))) + +; ============================================ +; getAllSuperclasses +; ============================================ + +(deftest testGetAllSuperclasses + (binding [typeSolver (typeSolverOnJar javaparser2)] + (let [scA (sampleClass "SC_A") + _ (assert scA) + scB (sampleClass "SC_B") + _ (assert scB) + scC (sampleClass "SC_C") + _ (assert scC)] + (is (= [scB scC] (getAllSuperclasses scA))) + (is (= [scC] (getAllSuperclasses scB))) + (is (= [] (getAllSuperclasses scC)))))) \ No newline at end of file diff --git a/test/effectivejava/test/symbol_solver/helper.clj b/test/effectivejava/test/symbol_solver/helper.clj new file mode 100644 index 0000000..b4eb055 --- /dev/null +++ b/test/effectivejava/test/symbol_solver/helper.clj @@ -0,0 +1,18 @@ +(ns effectivejava.test.symbol_solver.helper + (:use [effectivejava.jarloading]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.navigation]) + (:use [effectivejava.symbol_solver.funcs]) + (:use [effectivejava.symbol_solver.type_solver]) + (:use [effectivejava.symbol_solver.scope]) + (:use [effectivejava.utils]) + (:use [clojure.test])) + +(def javaparser2 "test-resources/sample-jars/javaparser-core-2.0.0.jar") +(def samplesCus (cus "test-resources/sample-codebases/samples/")) +(def sampleClasses (flatten (map allTypes samplesCus))) + +(defn sampleClass [name] + {:post [%]} + (first (filter (fn [c] (= name (.getName c))) sampleClasses))) diff --git a/test/app/test/symbol_solver/scope_test.clj b/test/effectivejava/test/symbol_solver/scope_test.clj similarity index 89% rename from test/app/test/symbol_solver/scope_test.clj rename to test/effectivejava/test/symbol_solver/scope_test.clj index 53f6bdc..4b50cb4 100644 --- a/test/app/test/symbol_solver/scope_test.clj +++ b/test/effectivejava/test/symbol_solver/scope_test.clj @@ -1,15 +1,15 @@ -(ns app.test.symbol_solver.scope_test - (:use [app.jarloading]) - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.javaparser.navigation]) - (:use [app.symbol_solver.funcs]) - (:use [app.symbol_solver.type_solver]) - (:use [app.symbol_solver.scope]) - (:use [app.utils]) - (:use [app.test.helper]) +(ns effectivejava.test.symbol_solver.scope_test + (:use [effectivejava.jarloading]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.javaparser.facade]) + (:use [effectivejava.symbol_solver.funcs]) + (:use [effectivejava.symbol_solver.type_solver]) + (:use [effectivejava.symbol_solver.scope]) + (:use [effectivejava.utils]) + (:use [effectivejava.test.helper]) (:use [clojure.test]) - (:use [app.test.symbol_solver.helper])) + (:use [effectivejava.test.symbol_solver.helper])) (deftest testSolveNameInVariableDeclarator (let [aClassResolvingToLocalVar (sampleClass "AClassResolvingToLocalVar") diff --git a/test/app/test/symbol_solver/type_solver_test.clj b/test/effectivejava/test/symbol_solver/type_solver_test.clj similarity index 79% rename from test/app/test/symbol_solver/type_solver_test.clj rename to test/effectivejava/test/symbol_solver/type_solver_test.clj index 65a9b3a..7eb7365 100644 --- a/test/app/test/symbol_solver/type_solver_test.clj +++ b/test/effectivejava/test/symbol_solver/type_solver_test.clj @@ -1,11 +1,11 @@ -(ns app.test.symbol_solver.type_solver_test - (:use [app.jarloading]) - (:use [app.model.protocols]) - (:use [app.model.javaparser]) - (:use [app.model.reflection]) - (:use [app.javaparser.navigation]) - (:use [app.symbol_solver.type_solver]) - (:use [app.model.javassist]) +(ns effectivejava.test.symbol_solver.type_solver_test + (:use [effectivejava.jarloading]) + (:use [effectivejava.model.protocols]) + (:use [effectivejava.model.javaparser]) + (:use [effectivejava.model.reflection]) + (:use [effectivejava.javaparser.facade]) + (:use [effectivejava.symbol_solver.type_solver]) + (:use [effectivejava.model.javassist]) (:use [clojure.test])) (def samplesCus (cus "test-resources/sample-codebases/type_solver_samples/")) diff --git a/test/app/test/utils.clj b/test/effectivejava/test/utils.clj similarity index 84% rename from test/app/test/utils.clj rename to test/effectivejava/test/utils.clj index 459656f..3dd24f2 100644 --- a/test/app/test/utils.clj +++ b/test/effectivejava/test/utils.clj @@ -1,6 +1,6 @@ -(ns app.test.utils +(ns effectivejava.test.utils (:use [clojure.test]) - (:use [app.utils])) + (:use [effectivejava.utils])) ; ============================================ ; preceedingChildren