diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f26b043..9072732b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,7 @@ Start by reading up on Exercism analyzers: To learn how the Java Analyzer works: +- Read the [project documentation][project-documentation]. - Browse [existing exercise analyzer implementations][browse-analyzers]. ### Writing comments @@ -46,3 +47,4 @@ See [the Analyzer comment guidelines][analyzer-comments-guidelines] for guidance [analyzer-docs-interface]: https://exercism.org/docs/building/tooling/analyzers/interface [community-docs]: https://exercism.org/docs/community/being-a-good-community-member [contributing-docs-github]: https://exercism.org/docs/building/github +[project-documentation]: https://exercism.github.io/java-analyzer/ diff --git a/README.md b/README.md index c341813a..ff3d31ef 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ The Java analyzer uses [Abstract Syntax Trees][ast-wiki] (ASTs) to analyze submitted solutions using the [`javaparser`][javaparser] library. +The documentation for this project can be found at [exercism.github.io/java-analyzer/][documentation]. + ## Contributing If you want to contribute to the Java analyzer, please refer to the [Contributing Guide][contributing-guide]. @@ -62,4 +64,5 @@ bin/run-tests-in-docker.sh [ast-wiki]: https://en.wikipedia.org/wiki/Abstract_syntax_tree [contributing-guide]: https://github.com/exercism/java-analyzer/blob/main/CONTRIBUTING.md -[javaparser]: https://github.com/javaparser/javaparser +[documentation]: https://exercism.github.io/java-analyzer/ +[javaparser]: https://github.com/javaparser/javaparser diff --git a/build.gradle b/build.gradle index 4aac700f..a40b39e3 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { group = "org.exercism" version = "1.0-SNAPSHOT" -mainClassName = "analyzer.Main" +mainClassName = "analyzer.AnalyzerCli" repositories { mavenCentral() diff --git a/src/doc/docs/index.md b/src/doc/docs/index.md index 9d2230a6..59f6a00b 100644 --- a/src/doc/docs/index.md +++ b/src/doc/docs/index.md @@ -1,3 +1,9 @@ # Welcome to exercism/java-analyzer Welcome to the Exercism Java analyzer documentation! 👋 + +!!! note + This documentation is still under construction. + In the meantime, we suggest having a look at the [Javadoc][javadoc]. + +[javadoc]: /java-analyzer/api/ diff --git a/src/main/java/analyzer/Analysis.java b/src/main/java/analyzer/Analysis.java index 50094c42..c219bbf7 100644 --- a/src/main/java/analyzer/Analysis.java +++ b/src/main/java/analyzer/Analysis.java @@ -5,6 +5,8 @@ import java.util.Set; /** + * This class is used to collect analysis results in the form of comments, tags and an optional summary. + * * @see The analyzer interface in the Exercism documentation */ public class Analysis { @@ -12,26 +14,63 @@ public class Analysis { private final Set comments = new LinkedHashSet<>(); private final Set tags = new LinkedHashSet<>(); + /** + * The summary is a short description of the complete analysis result. + * It is {@code null} by default. + * + * @return The summary if set, {@code null} otherwise. + */ public String getSummary() { return summary; } + /** + * Set the summary of the analysis. + * If the summary was set previously, setting it again will overwrite it. + * The summary can be cleared by setting it to {@code null}. + * + * @param summary The summary to set. + */ public void setSummary(String summary) { this.summary = summary; } + /** + * Retrieve a copy of the comments added to this analysis. + * The resulting list is guaranteed to contain no duplicates. + * + * @return List of comments. + */ public List getComments() { return List.copyOf(comments); } + /** + * Retrieve a copy of the tags added to this analysis. + * The resulting list is guaranteed to contain no duplicates. + * + * @return List of tags. + */ public List getTags() { return List.copyOf(tags); } + /** + * Add a new comment to the analysis. + * This does nothing if a comment with the same values was added previously. + * + * @param comment The comment to add. + */ public void addComment(Comment comment) { comments.add(comment); } + /** + * Add a new tag to the analysis. + * This does nothing if the same tag was added previously. + * + * @param tag The tag to add. + */ public void addTag(String tag) { tags.add(tag); } diff --git a/src/main/java/analyzer/Analyzer.java b/src/main/java/analyzer/Analyzer.java index b4e5c386..e8fb6fe8 100644 --- a/src/main/java/analyzer/Analyzer.java +++ b/src/main/java/analyzer/Analyzer.java @@ -1,9 +1,17 @@ package analyzer; -import com.github.javaparser.ast.CompilationUnit; - -import java.util.List; - +/** + * The {@code Analyzer} interface is used to implement both global and exercise-specific analyzers. + */ public interface Analyzer { - void analyze(List compilationUnits, Analysis analysis); + /** + * Analyze the given solution and append analysis results to the given analysis. + * The {@code analyze} method of each analyzer is invoked once for the whole submitted solution. + * + * @param solution The solution that should be analyzed. + * @param analysis The analysis instance used to collect results. + * This instance is shared across all analyzers, and should be used to add comments and tags, + * or set a summary. + */ + void analyze(Solution solution, Analysis analysis); } diff --git a/src/main/java/analyzer/AnalyzerCli.java b/src/main/java/analyzer/AnalyzerCli.java new file mode 100644 index 00000000..6f36d5fc --- /dev/null +++ b/src/main/java/analyzer/AnalyzerCli.java @@ -0,0 +1,50 @@ +package analyzer; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; + +/** + * The main entrypoint to the Java analyzer from the command-line. + * The CLI expects three arguments and is used like this: + * + *
+ * java -jar java-analyzer.jar exercise-slug /path/to/input/ /path/to/output/
+ * 
+ */ +public class AnalyzerCli { + + private static boolean isNotValidDirectory(String p) { + return !p.endsWith("/") || !new File(p).isDirectory(); + } + + public static void main(String... args) throws IOException { + if (args.length < 3) { + System.err.println("Invalid arguments. Usage: java-analyzer "); + System.exit(-1); + } + + String slug = args[0]; + String inputDirectory = args[1]; + String outputDirectory = args[2]; + + if (isNotValidDirectory(inputDirectory)) { + System.err.println("Invalid input directory. Must be a valid directory and end with a slash."); + System.exit(-1); + } + if (isNotValidDirectory(outputDirectory)) { + System.err.println("Invalid output directory. Must be a valid directory and end with a slash."); + System.exit(-1); + } + + var solution = new SubmittedSolution(slug, Path.of(inputDirectory)); + var analysis = AnalyzerRoot.analyze(solution); + + try (var analysisWriter = new FileWriter(Path.of(outputDirectory, "analysis.json").toFile()); + var tagsWriter = new FileWriter(Path.of(outputDirectory, "tags.json").toFile())) { + var output = new OutputWriter(analysisWriter, tagsWriter); + output.write(analysis); + } + } +} diff --git a/src/main/java/analyzer/AnalyzerRoot.java b/src/main/java/analyzer/AnalyzerRoot.java index 133d3052..57772cb6 100644 --- a/src/main/java/analyzer/AnalyzerRoot.java +++ b/src/main/java/analyzer/AnalyzerRoot.java @@ -6,21 +6,33 @@ import analyzer.exercises.lasagna.LasagnaAnalyzer; import analyzer.exercises.leap.LeapAnalyzer; import analyzer.exercises.twofer.TwoferAnalyzer; -import com.github.javaparser.ast.CompilationUnit; import java.util.ArrayList; import java.util.List; +/** + * The {@code AnalyzerRoot} is the initial entrypoint when analyzing a solution. + * Its job is to delegate the analysis of the parsed Java files to the global and exercise-specific analyzers. + */ public class AnalyzerRoot { - public static Analysis analyze(String slug, List compilationUnits) { + private AnalyzerRoot() { + } + + /** + * Perform the analysis of a solution. + * + * @param solution The solution being analyzed. + * @return The aggregated analysis of all applicable analyzers. + */ + public static Analysis analyze(Solution solution) { var analysis = new Analysis(); - for (Analyzer analyzer : createAnalyzers(slug)) { - analyzer.analyze(compilationUnits, analysis); + for (Analyzer analyzer : createAnalyzers(solution.getSlug())) { + analyzer.analyze(solution, analysis); } - if (analysis.getComments().stream().anyMatch(x -> x.getType() != CommentType.CELEBRATORY)) { + if (analysis.getComments().stream().anyMatch(x -> x.getType() != Comment.Type.CELEBRATORY)) { analysis.addComment(new FeedbackRequest()); } diff --git a/src/main/java/analyzer/Comment.java b/src/main/java/analyzer/Comment.java index 650f6331..a4d6569d 100644 --- a/src/main/java/analyzer/Comment.java +++ b/src/main/java/analyzer/Comment.java @@ -4,18 +4,82 @@ import java.util.Objects; /** + * The {@link Comment} class models a single comment in the analysis output. + * Each comment has a unique key that translates to a Markdown template in the + * exercism/website-copy repository. + *

+ * If the Markdown template contains any parameters, classes inheriting from {@link Comment} should override the + * {@link Comment#getParameters()} method to return the parameter keys and values specific to that template. + *

+ * Override the {@link Comment#getType()} method to change the {@link Type} associated to the comment. + * * @see The analyzer interface in the Exercism documentation + * @see Analyzer comments for the Java track in the website-copy */ public abstract class Comment { + /** + * The type of comment. + * Note that the order defined here corresponds to the order in which comments are sorted in the analyzer output. + * + * @see Documentation on comment types + */ + public enum Type { + ESSENTIAL, + ACTIONABLE, + INFORMATIVE, + CELEBRATORY + } + + /** + * The comment key is a {@link String} that uniquely identifies the comment. + *

+ * Comment keys use the format {@code "java.."}. + * The {@code } can be either {@code general} for general comments, + * or the slug of the exercise for exercise-specific comments. + * The {@code } specifies the name of the comment. + *

+ * The combination of {@code } and {@code } must be unique, and defines the location of the + * Markdown template in the exercism/website-copy repository. + *

+ * For example, the comment key {@code "java.hello-world.foo_bar"} would translate to the Markdown file at + * {@code analyzer-comments/java/hello-world/foo_bar.md}. + * + * @return The unique comment key. + */ public abstract String getKey(); + /** + * Each parameter in the Markdown template should have a corresponding parameter in the comment. + * Parameters in Markdown templates are of the form {@code %s}. + *

+ * For example, if the Markdown template contains a parameter {@code %s}, + * the implementation of this method could look like this: + *

{@code
+     *     public Map getParameters() {
+     *         return Map.of("methodName", "theNameOfTheMethod");
+     *     }
+     * }
+ * + * @return The parameters for the comment. + */ public Map getParameters() { return Map.of(); } - public CommentType getType() { - return CommentType.INFORMATIVE; + /** + *
    + *
  • Use {@link Type#ESSENTIAL} to instruct students that they must address it.
  • + *
  • Use {@link Type#ACTIONABLE} to instruct students that they could improve their solution by addressing it.
  • + *
  • Use {@link Type#INFORMATIVE} to give students extra information without expecting them to use it.
  • + *
  • Use {@link Type#CELEBRATORY} to tell students that they did something right.
  • + *
+ * + * @return The type of the comment. + * @see Documentation on comment types + */ + public Type getType() { + return Type.INFORMATIVE; } @Override diff --git a/src/main/java/analyzer/CommentType.java b/src/main/java/analyzer/CommentType.java deleted file mode 100644 index 9d50a567..00000000 --- a/src/main/java/analyzer/CommentType.java +++ /dev/null @@ -1,11 +0,0 @@ -package analyzer; - -/** - * @see The analyzer interface in the Exercism documentation - */ -public enum CommentType { - ESSENTIAL, - ACTIONABLE, - INFORMATIVE, - CELEBRATORY -} diff --git a/src/main/java/analyzer/Main.java b/src/main/java/analyzer/Main.java deleted file mode 100644 index 911c6cc7..00000000 --- a/src/main/java/analyzer/Main.java +++ /dev/null @@ -1,68 +0,0 @@ -package analyzer; - -import com.github.javaparser.ParseResult; -import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.utils.SourceRoot; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -public class Main { - - private static boolean isNotValidDirectory(String p) { - return !p.endsWith("/") || !new File(p).isDirectory(); - } - - private static Options validateOptions(String... args) { - if (args.length < 3) { - System.err.println("Invalid arguments. Usage: java-analyzer "); - System.exit(-1); - } - - String slug = args[0]; - String inputDirectory = args[1]; - String outputDirectory = args[2]; - - if (isNotValidDirectory(inputDirectory)) { - System.err.println("Invalid input directory. Must be a valid directory and end with a slash."); - System.exit(-1); - } - if (isNotValidDirectory(outputDirectory)) { - System.err.println("Invalid output directory. Must be a valid directory and end with a slash."); - System.exit(-1); - } - - return new Options(slug, inputDirectory, outputDirectory); - } - - private static List parseInput(Options options) throws IOException { - var sourceRoot = new SourceRoot(Path.of(options.inputDirectory, "src/main/java")); - var compilationUnits = new ArrayList(); - for (ParseResult parseResult : sourceRoot.tryToParse()) { - compilationUnits.add(parseResult.getResult().get()); - } - - return List.copyOf(compilationUnits); - } - - private static void writeOutput(Analysis analysis, Options options) throws IOException { - try (var analysisWriter = new FileWriter(options.outputDirectory + "analysis.json"); - var tagsWriter = new FileWriter(options.outputDirectory + "tags.json")) { - var output = new OutputWriter(analysisWriter, tagsWriter); - output.write(analysis); - } - } - - public static void main(String... args) throws IOException { - var options = validateOptions(args); - var input = parseInput(options); - var analysis = AnalyzerRoot.analyze(options.slug, input); - writeOutput(analysis, options); - } - - private record Options(String slug, String inputDirectory, String outputDirectory){} -} diff --git a/src/main/java/analyzer/OutputWriter.java b/src/main/java/analyzer/OutputWriter.java index eb4ebfe7..7eeed5b9 100644 --- a/src/main/java/analyzer/OutputWriter.java +++ b/src/main/java/analyzer/OutputWriter.java @@ -7,6 +7,8 @@ import java.util.Optional; /** + * The {@link OutputWriter} converts the analysis result into JSON output and writes it to the writers passed to the constructor. + * * @see The analyzer interface in the Exercism documentation */ public class OutputWriter { @@ -68,8 +70,8 @@ private static JSONObject serialize(Comment comment) { } private static int compareCommentsByType(Comment a, Comment b) { - var ordinalA = Optional.ofNullable(a.getType()).map(CommentType::ordinal).orElse(Integer.MAX_VALUE); - var ordinalB = Optional.ofNullable(b.getType()).map(CommentType::ordinal).orElse(Integer.MAX_VALUE); + var ordinalA = Optional.ofNullable(a.getType()).map(Comment.Type::ordinal).orElse(Integer.MAX_VALUE); + var ordinalB = Optional.ofNullable(b.getType()).map(Comment.Type::ordinal).orElse(Integer.MAX_VALUE); return Integer.compare(ordinalA, ordinalB); } } diff --git a/src/main/java/analyzer/Solution.java b/src/main/java/analyzer/Solution.java new file mode 100644 index 00000000..b7b90db8 --- /dev/null +++ b/src/main/java/analyzer/Solution.java @@ -0,0 +1,24 @@ +package analyzer; + +import com.github.javaparser.ast.CompilationUnit; + +import java.util.List; + +/** + * This represents the solution to an exercise. + */ +public interface Solution { + /** + * Get the slug of the exercise to which the solution belongs. + * + * @return Exercise slug. + */ + String getSlug(); + + /** + * Get the parsed Java files that are part of the solution. + * + * @return List of compilation units. + */ + List getCompilationUnits(); +} diff --git a/src/main/java/analyzer/SubmittedSolution.java b/src/main/java/analyzer/SubmittedSolution.java new file mode 100644 index 00000000..307f4c52 --- /dev/null +++ b/src/main/java/analyzer/SubmittedSolution.java @@ -0,0 +1,40 @@ +package analyzer; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.utils.SourceRoot; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * This represents a solution submitted by a student. + * It parses all Java files in the source root of the input directory. + */ +class SubmittedSolution implements Solution { + private static final String SOURCE_ROOT_PATH = "src/main/java"; + + private final String slug; + private final List compilationUnits; + + SubmittedSolution(String slug, Path inputDirectory) throws IOException { + this.slug = slug; + this.compilationUnits = new ArrayList<>(); + + var sourceRoot = new SourceRoot(inputDirectory.resolve(SOURCE_ROOT_PATH)); + for (var result : sourceRoot.tryToParse()) { + this.compilationUnits.add(result.getResult().get()); + } + } + + @Override + public String getSlug() { + return this.slug; + } + + @Override + public List getCompilationUnits() { + return List.copyOf(this.compilationUnits); + } +} diff --git a/src/main/java/analyzer/comments/AvoidHardCodedTestCases.java b/src/main/java/analyzer/comments/AvoidHardCodedTestCases.java index 333a3740..9ecaa85b 100644 --- a/src/main/java/analyzer/comments/AvoidHardCodedTestCases.java +++ b/src/main/java/analyzer/comments/AvoidHardCodedTestCases.java @@ -1,9 +1,10 @@ package analyzer.comments; import analyzer.Comment; -import analyzer.CommentType; /** + * This comment instructs students not to hard-code values used in the exercise tests. + * * @see Markdown Template */ public class AvoidHardCodedTestCases extends Comment { @@ -13,7 +14,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ESSENTIAL; + public Type getType() { + return Type.ESSENTIAL; } } diff --git a/src/main/java/analyzer/comments/AvoidPrintStatements.java b/src/main/java/analyzer/comments/AvoidPrintStatements.java index 638ce14b..f78bea0d 100644 --- a/src/main/java/analyzer/comments/AvoidPrintStatements.java +++ b/src/main/java/analyzer/comments/AvoidPrintStatements.java @@ -1,9 +1,11 @@ package analyzer.comments; import analyzer.Comment; -import analyzer.CommentType; /** + * This comment instructs students not to use print statements in order to debug their solution. + * Instead they should use proper debugging tools. + * * @see Markdown Template */ public class AvoidPrintStatements extends Comment { @@ -11,9 +13,4 @@ public class AvoidPrintStatements extends Comment { public String getKey() { return "java.general.avoid_print_statements"; } - - @Override - public CommentType getType() { - return CommentType.INFORMATIVE; - } } diff --git a/src/main/java/analyzer/comments/ConstructorTooLong.java b/src/main/java/analyzer/comments/ConstructorTooLong.java index b67c3f12..a990732a 100644 --- a/src/main/java/analyzer/comments/ConstructorTooLong.java +++ b/src/main/java/analyzer/comments/ConstructorTooLong.java @@ -1,13 +1,15 @@ package analyzer.comments; import analyzer.Comment; -import analyzer.CommentType; import java.util.Collection; import java.util.List; import java.util.Map; /** + * This comment indicates that the code in the solution's constructor is too long, + * and that it would benefit from being broken up into helper methods. + * * @see Markdown Template */ public class ConstructorTooLong extends Comment { @@ -32,7 +34,7 @@ public Map getParameters() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/comments/DoNotUseMainMethod.java b/src/main/java/analyzer/comments/DoNotUseMainMethod.java index 4c738550..8584626d 100644 --- a/src/main/java/analyzer/comments/DoNotUseMainMethod.java +++ b/src/main/java/analyzer/comments/DoNotUseMainMethod.java @@ -1,9 +1,12 @@ package analyzer.comments; import analyzer.Comment; -import analyzer.CommentType; /** + * Use this comment if a solution has defined a static {@code main} method. + * This may indicate that the students have been debugging their code this way, + * instead of using proper debugging tools. + * * @see Markdown Template */ public class DoNotUseMainMethod extends Comment { @@ -13,7 +16,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ESSENTIAL; + public Type getType() { + return Type.ESSENTIAL; } } diff --git a/src/main/java/analyzer/comments/ExemplarSolution.java b/src/main/java/analyzer/comments/ExemplarSolution.java index fee8c1bb..2f04c3bf 100644 --- a/src/main/java/analyzer/comments/ExemplarSolution.java +++ b/src/main/java/analyzer/comments/ExemplarSolution.java @@ -1,11 +1,13 @@ package analyzer.comments; import analyzer.Comment; -import analyzer.CommentType; import java.util.Map; /** + * Use this comment if the solution is comparable to the exemplar solution. + * This comment is generally used for concept exercises. + * * @see Markdown Template */ public class ExemplarSolution extends Comment { @@ -26,7 +28,7 @@ public Map getParameters() { } @Override - public CommentType getType() { - return CommentType.CELEBRATORY; + public Type getType() { + return Type.CELEBRATORY; } } diff --git a/src/main/java/analyzer/comments/FeedbackRequest.java b/src/main/java/analyzer/comments/FeedbackRequest.java index 86549172..4d8fac39 100644 --- a/src/main/java/analyzer/comments/FeedbackRequest.java +++ b/src/main/java/analyzer/comments/FeedbackRequest.java @@ -3,6 +3,9 @@ import analyzer.Comment; /** + * This comment requests students to leave feedback on the analyzer comments. + * Use it whenever new comments or analyzers are implemented, as a way to "beta-test" the new functionality. + * * @see Markdown Template */ public class FeedbackRequest extends Comment { diff --git a/src/main/java/analyzer/comments/MethodTooLong.java b/src/main/java/analyzer/comments/MethodTooLong.java index adb83d8d..c4a64800 100644 --- a/src/main/java/analyzer/comments/MethodTooLong.java +++ b/src/main/java/analyzer/comments/MethodTooLong.java @@ -1,13 +1,15 @@ package analyzer.comments; import analyzer.Comment; -import analyzer.CommentType; import java.util.Collection; import java.util.List; import java.util.Map; /** + * This comment indicates that a method in the solution is too long, + * and that it could benefit from being split up into multiple helper methods. + * * @see Markdown Template */ public class MethodTooLong extends Comment { @@ -32,7 +34,7 @@ public Map getParameters() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/comments/RemoveTodoComments.java b/src/main/java/analyzer/comments/RemoveTodoComments.java index 2279b5ae..15b82ebc 100644 --- a/src/main/java/analyzer/comments/RemoveTodoComments.java +++ b/src/main/java/analyzer/comments/RemoveTodoComments.java @@ -1,9 +1,10 @@ package analyzer.comments; import analyzer.Comment; -import analyzer.CommentType; /** + * This comment instructs students to remove lef-over to-do comments from their solution. + * * @see Markdown Template */ public class RemoveTodoComments extends Comment { @@ -13,7 +14,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/comments/UseProperClassName.java b/src/main/java/analyzer/comments/UseProperClassName.java deleted file mode 100644 index 435f575f..00000000 --- a/src/main/java/analyzer/comments/UseProperClassName.java +++ /dev/null @@ -1,32 +0,0 @@ -package analyzer.comments; - -import analyzer.Comment; -import analyzer.CommentType; - -import java.util.Map; - -/** - * @see Markdown Template - */ -public class UseProperClassName extends Comment { - private final String className; - - public UseProperClassName(String className) { - this.className = className; - } - - @Override - public String getKey() { - return "java.general.use_proper_class_name"; - } - - @Override - public Map getParameters() { - return Map.of("className", className); - } - - @Override - public CommentType getType() { - return CommentType.ESSENTIAL; - } -} diff --git a/src/main/java/analyzer/comments/UseProperMethodName.java b/src/main/java/analyzer/comments/UseProperMethodName.java deleted file mode 100644 index 461c631a..00000000 --- a/src/main/java/analyzer/comments/UseProperMethodName.java +++ /dev/null @@ -1,32 +0,0 @@ -package analyzer.comments; - -import analyzer.Comment; -import analyzer.CommentType; - -import java.util.Map; - -/** - * @see Markdown Template - */ -public class UseProperMethodName extends Comment { - private final String methodName; - - public UseProperMethodName(String methodName) { - this.methodName = methodName; - } - - @Override - public String getKey() { - return "java.general.use_proper_method_name"; - } - - @Override - public Map getParameters() { - return Map.of("methodName", this.methodName); - } - - @Override - public CommentType getType() { - return CommentType.ESSENTIAL; - } -} diff --git a/src/main/java/analyzer/exercises/GlobalAnalyzer.java b/src/main/java/analyzer/exercises/GlobalAnalyzer.java index 0f31f99b..6bdc0e6b 100644 --- a/src/main/java/analyzer/exercises/GlobalAnalyzer.java +++ b/src/main/java/analyzer/exercises/GlobalAnalyzer.java @@ -2,6 +2,7 @@ import analyzer.Analysis; import analyzer.Analyzer; +import analyzer.Solution; import analyzer.comments.AvoidPrintStatements; import analyzer.comments.DoNotUseMainMethod; import com.github.javaparser.ast.CompilationUnit; @@ -9,13 +10,16 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; -import java.util.List; - +/** + * The {@link GlobalAnalyzer} contains checks that are exercise-agnostic, + * such as whether a solution is using print statements or a static {@code main} method. + * It extends from the {@link VoidVisitorAdapter} and uses the visitor pattern to traverse each compilation unit. + */ public class GlobalAnalyzer extends VoidVisitorAdapter implements Analyzer { @Override - public void analyze(List compilationUnits, Analysis analysis) { - for (CompilationUnit compilationUnit : compilationUnits) { + public void analyze(Solution solution, Analysis analysis) { + for (CompilationUnit compilationUnit : solution.getCompilationUnits()) { compilationUnit.accept(this, analysis); } } diff --git a/src/main/java/analyzer/exercises/hamming/AvoidCharacterLiterals.java b/src/main/java/analyzer/exercises/hamming/AvoidCharacterLiterals.java index 95e58832..a582b008 100644 --- a/src/main/java/analyzer/exercises/hamming/AvoidCharacterLiterals.java +++ b/src/main/java/analyzer/exercises/hamming/AvoidCharacterLiterals.java @@ -1,7 +1,6 @@ package analyzer.exercises.hamming; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/exercises/hamming/CalculateDistanceInConstructor.java b/src/main/java/analyzer/exercises/hamming/CalculateDistanceInConstructor.java index 3f8c6143..fe6d149e 100644 --- a/src/main/java/analyzer/exercises/hamming/CalculateDistanceInConstructor.java +++ b/src/main/java/analyzer/exercises/hamming/CalculateDistanceInConstructor.java @@ -1,7 +1,6 @@ package analyzer.exercises.hamming; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java b/src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java index 2dc1ca79..9739d4cb 100644 --- a/src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java +++ b/src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java @@ -2,33 +2,25 @@ import analyzer.Analysis; import analyzer.Analyzer; +import analyzer.Solution; import analyzer.comments.ConstructorTooLong; import analyzer.comments.MethodTooLong; -import analyzer.comments.UseProperClassName; -import analyzer.comments.UseProperMethodName; -import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; -import java.util.List; import java.util.Set; +/** + * The {@link HammingAnalyzer} is the analyzer implementation for the {@code hamming} practice exercise. + * + * @see The hamming exercise on the Java track + */ public class HammingAnalyzer implements Analyzer { @Override - public void analyze(List compilationUnits, Analysis analysis) { + public void analyze(Solution solution, Analysis analysis) { HammingWalker walker = new HammingWalker(); - compilationUnits.forEach(cu -> cu.walk(ClassOrInterfaceDeclaration.class, walker)); - - if (!walker.hasHammingClass()) { - analysis.addComment(new UseProperClassName("Hamming")); - return; - } - - if (!walker.hasGetHammingDistanceMethod()) { - analysis.addComment(new UseProperMethodName("getHammingDistance")); - return; - } + solution.getCompilationUnits().forEach(cu -> cu.walk(ClassOrInterfaceDeclaration.class, walker)); if (!walker.hasConstructor()) { analysis.addComment(new MustUseConstructor()); @@ -46,7 +38,7 @@ public void analyze(List compilationUnits, Analysis analysis) { } if (!walker.getHammingDistanceMethodMayCalculateDistance() - && !walker.constructorMayCalculateDistance()) { + && !walker.constructorMayCalculateDistance()) { analysis.addComment(new MustCalculateHammingDistance()); return; } diff --git a/src/main/java/analyzer/exercises/hamming/HammingWalker.java b/src/main/java/analyzer/exercises/hamming/HammingWalker.java index a3d39927..25180374 100644 --- a/src/main/java/analyzer/exercises/hamming/HammingWalker.java +++ b/src/main/java/analyzer/exercises/hamming/HammingWalker.java @@ -1,7 +1,5 @@ package analyzer.exercises.hamming; -import static com.google.common.collect.ImmutableSet.toImmutableSet; - import com.github.javaparser.Range; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; @@ -10,19 +8,12 @@ import com.github.javaparser.ast.expr.LambdaExpr; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.nodeTypes.NodeWithRange; -import com.github.javaparser.ast.stmt.BlockStmt; -import com.github.javaparser.ast.stmt.ForEachStmt; -import com.github.javaparser.ast.stmt.ForStmt; -import com.github.javaparser.ast.stmt.Statement; -import com.github.javaparser.ast.stmt.ThrowStmt; -import com.github.javaparser.ast.stmt.WhileStmt; +import com.github.javaparser.ast.stmt.*; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; -import static com.google.common.collect.MoreCollectors.toOptional; - import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -30,17 +21,19 @@ import java.util.function.Consumer; import java.util.stream.Stream; +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.MoreCollectors.toOptional; + class HammingWalker implements Consumer { private ClassOrInterfaceDeclaration hammingClass; private List constructors = ImmutableList.of(); private ConstructorDeclaration constructor; private ListMultimap methods = ImmutableListMultimap.of(); - private Set methodsCalledByConstructor = new HashSet<>(); + private final Set methodsCalledByConstructor = new HashSet<>(); private boolean constructorHasIfStatements; private boolean constructorThrowsIllegalArgumentDirectly; private boolean constructorMayCalculateDistanceDirectly; - private MethodDeclaration getHammingDistanceMethod; - private Set methodsCalledByGetHammingDistance = new HashSet<>(); + private final Set methodsCalledByGetHammingDistance = new HashSet<>(); private boolean getHammingDistanceMayCalculateDistanceDirectly; @Override @@ -128,8 +121,6 @@ private Optional findGetHammingDistanceMethod() { } private void walkGetHammingDistanceMethod(MethodDeclaration getHammingDistanceMethod) { - this.getHammingDistanceMethod = getHammingDistanceMethod; - getHammingDistanceMethod.getBody().ifPresent(this::walkGetHammingDistanceMethod); } @@ -178,14 +169,6 @@ private Stream getMethodsCalledBy(BlockStmt body) { .flatMap(this::getMethodCallNames); } - public boolean hasHammingClass() { - return hammingClass != null; - } - - public boolean hasGetHammingDistanceMethod() { - return getHammingDistanceMethod != null; - } - public boolean hasConstructor() { return constructor != null; } diff --git a/src/main/java/analyzer/exercises/hamming/MustCalculateHammingDistance.java b/src/main/java/analyzer/exercises/hamming/MustCalculateHammingDistance.java index 5816c9c3..4c547122 100644 --- a/src/main/java/analyzer/exercises/hamming/MustCalculateHammingDistance.java +++ b/src/main/java/analyzer/exercises/hamming/MustCalculateHammingDistance.java @@ -1,7 +1,6 @@ package analyzer.exercises.hamming; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/exercises/hamming/MustThrowInConstructor.java b/src/main/java/analyzer/exercises/hamming/MustThrowInConstructor.java index 1f13588d..419d48c8 100644 --- a/src/main/java/analyzer/exercises/hamming/MustThrowInConstructor.java +++ b/src/main/java/analyzer/exercises/hamming/MustThrowInConstructor.java @@ -1,7 +1,6 @@ package analyzer.exercises.hamming; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ESSENTIAL; + public Type getType() { + return Type.ESSENTIAL; } } diff --git a/src/main/java/analyzer/exercises/hamming/MustUseConditionalLogicInConstructor.java b/src/main/java/analyzer/exercises/hamming/MustUseConditionalLogicInConstructor.java index 4bf9b81c..33bd29ab 100644 --- a/src/main/java/analyzer/exercises/hamming/MustUseConditionalLogicInConstructor.java +++ b/src/main/java/analyzer/exercises/hamming/MustUseConditionalLogicInConstructor.java @@ -1,7 +1,6 @@ package analyzer.exercises.hamming; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ESSENTIAL; + public Type getType() { + return Type.ESSENTIAL; } } diff --git a/src/main/java/analyzer/exercises/hamming/MustUseConstructor.java b/src/main/java/analyzer/exercises/hamming/MustUseConstructor.java index 461a1244..061e5318 100644 --- a/src/main/java/analyzer/exercises/hamming/MustUseConstructor.java +++ b/src/main/java/analyzer/exercises/hamming/MustUseConstructor.java @@ -1,7 +1,6 @@ package analyzer.exercises.hamming; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ESSENTIAL; + public Type getType() { + return Type.ESSENTIAL; } } diff --git a/src/main/java/analyzer/exercises/hamming/MustUseStringCharAtOrCodePointAt.java b/src/main/java/analyzer/exercises/hamming/MustUseStringCharAtOrCodePointAt.java index bc53fa71..99ebfe47 100644 --- a/src/main/java/analyzer/exercises/hamming/MustUseStringCharAtOrCodePointAt.java +++ b/src/main/java/analyzer/exercises/hamming/MustUseStringCharAtOrCodePointAt.java @@ -1,7 +1,6 @@ package analyzer.exercises.hamming; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/exercises/hamming/ShouldUseStreamFilterAndCount.java b/src/main/java/analyzer/exercises/hamming/ShouldUseStreamFilterAndCount.java index 0687fefa..79a184b6 100644 --- a/src/main/java/analyzer/exercises/hamming/ShouldUseStreamFilterAndCount.java +++ b/src/main/java/analyzer/exercises/hamming/ShouldUseStreamFilterAndCount.java @@ -1,7 +1,6 @@ package analyzer.exercises.hamming; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/exercises/hamming/ShouldUseStringIsEmpty.java b/src/main/java/analyzer/exercises/hamming/ShouldUseStringIsEmpty.java index 1673601d..2595ea02 100644 --- a/src/main/java/analyzer/exercises/hamming/ShouldUseStringIsEmpty.java +++ b/src/main/java/analyzer/exercises/hamming/ShouldUseStringIsEmpty.java @@ -1,7 +1,6 @@ package analyzer.exercises.hamming; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -11,9 +10,4 @@ class ShouldUseStringIsEmpty extends Comment { public String getKey() { return "java.hamming.should_use_string_is_empty"; } - - @Override - public CommentType getType() { - return CommentType.INFORMATIVE; - } } diff --git a/src/main/java/analyzer/exercises/lasagna/LasagnaAnalyzer.java b/src/main/java/analyzer/exercises/lasagna/LasagnaAnalyzer.java index bf55a429..855ccabe 100644 --- a/src/main/java/analyzer/exercises/lasagna/LasagnaAnalyzer.java +++ b/src/main/java/analyzer/exercises/lasagna/LasagnaAnalyzer.java @@ -2,6 +2,7 @@ import analyzer.Analysis; import analyzer.Analyzer; +import analyzer.Solution; import analyzer.comments.ExemplarSolution; import analyzer.comments.RemoveTodoComments; import com.github.javaparser.ast.CompilationUnit; @@ -10,8 +11,12 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; -import java.util.List; - +/** + * The {@link LasagnaAnalyzer} is the analyzer implementation for the {@code lasagna} concept exercise. + * It extends from the {@link VoidVisitorAdapter} and uses the visitor pattern to traverse each compilation unit. + * + * @see The lasagna exercise on the Java track + */ public class LasagnaAnalyzer extends VoidVisitorAdapter implements Analyzer { private static final String EXERCISE_NAME = "Lasagna"; private static final String EXPECTED_MINUTES_IN_OVEN = "expectedMinutesInOven"; @@ -20,8 +25,8 @@ public class LasagnaAnalyzer extends VoidVisitorAdapter implements Ana private static final String TOTAL_TIME_IN_MINUTES = "totalTimeInMinutes"; @Override - public void analyze(List compilationUnits, Analysis analysis) { - for (CompilationUnit compilationUnit : compilationUnits) { + public void analyze(Solution solution, Analysis analysis) { + for (CompilationUnit compilationUnit : solution.getCompilationUnits()) { compilationUnit.accept(this, analysis); } diff --git a/src/main/java/analyzer/exercises/lasagna/ReuseCode.java b/src/main/java/analyzer/exercises/lasagna/ReuseCode.java index 6dbd3bbb..7a2fdf89 100644 --- a/src/main/java/analyzer/exercises/lasagna/ReuseCode.java +++ b/src/main/java/analyzer/exercises/lasagna/ReuseCode.java @@ -1,7 +1,6 @@ package analyzer.exercises.lasagna; import analyzer.Comment; -import analyzer.CommentType; import java.util.Map; @@ -31,7 +30,7 @@ public Map getParameters() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/exercises/leap/AvoidConditionalLogic.java b/src/main/java/analyzer/exercises/leap/AvoidConditionalLogic.java index ce79baab..161de18f 100644 --- a/src/main/java/analyzer/exercises/leap/AvoidConditionalLogic.java +++ b/src/main/java/analyzer/exercises/leap/AvoidConditionalLogic.java @@ -1,7 +1,6 @@ package analyzer.exercises.leap; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/exercises/leap/LeapAnalyzer.java b/src/main/java/analyzer/exercises/leap/LeapAnalyzer.java index fd5289cf..e3830c9f 100644 --- a/src/main/java/analyzer/exercises/leap/LeapAnalyzer.java +++ b/src/main/java/analyzer/exercises/leap/LeapAnalyzer.java @@ -2,6 +2,7 @@ import analyzer.Analysis; import analyzer.Analyzer; +import analyzer.Solution; import analyzer.comments.AvoidHardCodedTestCases; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.ImportDeclaration; @@ -13,9 +14,14 @@ import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import java.util.HashSet; -import java.util.List; import java.util.Set; +/** + * The {@link LeapAnalyzer} is the analyzer implementation for the {@code leap} practice exercise. + * It extends from the {@link VoidVisitorAdapter} and uses the visitor pattern to traverse each compilation unit. + * + * @see The leap exercise on the Java track + */ public class LeapAnalyzer extends VoidVisitorAdapter implements Analyzer { private static final Set TEST_CASES = Set.of(1960, 1996, 2000, 2400); private static final Set DISALLOWED_IMPORTS = Set.of( @@ -26,8 +32,8 @@ public class LeapAnalyzer extends VoidVisitorAdapter implements Analyz private final Set intLiterals = new HashSet<>(); @Override - public void analyze(List compilationUnits, Analysis analysis) { - for (CompilationUnit compilationUnit : compilationUnits) { + public void analyze(Solution solution, Analysis analysis) { + for (CompilationUnit compilationUnit : solution.getCompilationUnits()) { compilationUnit.accept(this, analysis); } } diff --git a/src/main/java/analyzer/exercises/leap/NoBuiltInMethods.java b/src/main/java/analyzer/exercises/leap/NoBuiltInMethods.java index d7181cf7..7e14d35e 100644 --- a/src/main/java/analyzer/exercises/leap/NoBuiltInMethods.java +++ b/src/main/java/analyzer/exercises/leap/NoBuiltInMethods.java @@ -1,7 +1,6 @@ package analyzer.exercises.leap; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ESSENTIAL; + public Type getType() { + return Type.ESSENTIAL; } } diff --git a/src/main/java/analyzer/exercises/leap/UseMinimumNumberOfChecks.java b/src/main/java/analyzer/exercises/leap/UseMinimumNumberOfChecks.java index 7a909b0a..f5b6b733 100644 --- a/src/main/java/analyzer/exercises/leap/UseMinimumNumberOfChecks.java +++ b/src/main/java/analyzer/exercises/leap/UseMinimumNumberOfChecks.java @@ -1,7 +1,6 @@ package analyzer.exercises.leap; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/exercises/twofer/AvoidStringFormat.java b/src/main/java/analyzer/exercises/twofer/AvoidStringFormat.java index d3f9fc2c..8ccd1cd5 100644 --- a/src/main/java/analyzer/exercises/twofer/AvoidStringFormat.java +++ b/src/main/java/analyzer/exercises/twofer/AvoidStringFormat.java @@ -1,7 +1,6 @@ package analyzer.exercises.twofer; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java b/src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java index a2b5a93f..b076b144 100644 --- a/src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java +++ b/src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java @@ -2,26 +2,23 @@ import analyzer.Analysis; import analyzer.Analyzer; +import analyzer.Solution; import analyzer.comments.AvoidHardCodedTestCases; -import analyzer.comments.UseProperClassName; -import analyzer.comments.UseProperMethodName; -import com.github.javaparser.ast.CompilationUnit; - -import java.util.List; +/** + * The {@link TwoferAnalyzer} is the analyzer implementation for the {@code two-fer} practice exercise. + * + * @see The two-fer exercise on the Java track + */ public class TwoferAnalyzer implements Analyzer { @Override - public void analyze(List compilationUnits, Analysis analysis) { + public void analyze(Solution solution, Analysis analysis) { TwoferWalker walker = new TwoferWalker(); - compilationUnits.forEach(cu -> cu.walk(walker)); + solution.getCompilationUnits().forEach(cu -> cu.walk(walker)); - if (!walker.hasClassTwofer) { - analysis.addComment(new UseProperClassName("Twofer")); - } else if (!walker.hasMethodTwofer) { - analysis.addComment(new UseProperMethodName("twofer")); - } else if (walker.hasHardCodedTestCases) { + if (walker.hasHardCodedTestCases) { analysis.addComment(new AvoidHardCodedTestCases()); } else if (walker.usesLambda) { // could be used later for additional comments? diff --git a/src/main/java/analyzer/exercises/twofer/TwoferWalker.java b/src/main/java/analyzer/exercises/twofer/TwoferWalker.java index 0be43980..38536a92 100644 --- a/src/main/java/analyzer/exercises/twofer/TwoferWalker.java +++ b/src/main/java/analyzer/exercises/twofer/TwoferWalker.java @@ -1,15 +1,15 @@ package analyzer.exercises.twofer; import com.github.javaparser.ast.Node; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; -import com.github.javaparser.ast.body.MethodDeclaration; -import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.expr.ConditionalExpr; +import com.github.javaparser.ast.expr.LambdaExpr; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.stmt.*; + import java.util.function.Consumer; class TwoferWalker implements Consumer { - boolean hasClassTwofer; - boolean hasMethodTwofer; boolean hasHardCodedTestCases; boolean usesIfStatement; boolean usesConditional; @@ -21,11 +21,7 @@ class TwoferWalker implements Consumer { @Override public void accept(Node node) { - if (node instanceof ClassOrInterfaceDeclaration) { - this.hasClassTwofer = ((ClassOrInterfaceDeclaration) node).getName().toString().equals("Twofer"); - } else if (node instanceof MethodDeclaration methodDeclaration && methodDeclaration.getNameAsString().equals("twofer")) { - this.hasMethodTwofer = true; - } else if (node instanceof StringLiteralExpr && !this.hasHardCodedTestCases) { + if (node instanceof StringLiteralExpr && !this.hasHardCodedTestCases) { this.hasHardCodedTestCases = node.toString().contains("Alice") || node.toString().contains("Bob"); } else if (node instanceof ReturnStmt) { this.returnCount++; diff --git a/src/main/java/analyzer/exercises/twofer/UseConditionalLogic.java b/src/main/java/analyzer/exercises/twofer/UseConditionalLogic.java index 2dedd0e0..50a432a9 100644 --- a/src/main/java/analyzer/exercises/twofer/UseConditionalLogic.java +++ b/src/main/java/analyzer/exercises/twofer/UseConditionalLogic.java @@ -1,7 +1,6 @@ package analyzer.exercises.twofer; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ESSENTIAL; + public Type getType() { + return Type.ESSENTIAL; } } diff --git a/src/main/java/analyzer/exercises/twofer/UseOneReturn.java b/src/main/java/analyzer/exercises/twofer/UseOneReturn.java index aa86f373..c1091ade 100644 --- a/src/main/java/analyzer/exercises/twofer/UseOneReturn.java +++ b/src/main/java/analyzer/exercises/twofer/UseOneReturn.java @@ -1,7 +1,6 @@ package analyzer.exercises.twofer; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/main/java/analyzer/exercises/twofer/UseTernaryOperator.java b/src/main/java/analyzer/exercises/twofer/UseTernaryOperator.java index 7e44d77a..35e0b8b9 100644 --- a/src/main/java/analyzer/exercises/twofer/UseTernaryOperator.java +++ b/src/main/java/analyzer/exercises/twofer/UseTernaryOperator.java @@ -1,7 +1,6 @@ package analyzer.exercises.twofer; import analyzer.Comment; -import analyzer.CommentType; /** * @see Markdown Template @@ -13,7 +12,7 @@ public String getKey() { } @Override - public CommentType getType() { - return CommentType.ACTIONABLE; + public Type getType() { + return Type.ACTIONABLE; } } diff --git a/src/test/java/analyzer/AnalyzerTest.java b/src/test/java/analyzer/AnalyzerTest.java deleted file mode 100644 index 5c657e81..00000000 --- a/src/test/java/analyzer/AnalyzerTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package analyzer; - -import com.github.javaparser.StaticJavaParser; -import com.github.javaparser.ast.CompilationUnit; - -import java.lang.reflect.InvocationTargetException; -import java.util.List; - -public class AnalyzerTest { - private final Class analyzerClass; - - public AnalyzerTest(Class analyzerClass) { - this.analyzerClass = analyzerClass; - } - - protected Analysis analyzeResourceFile(String resourceFileName) { - var resource = getClass().getResourceAsStream(resourceFileName); - return analyze(StaticJavaParser.parse(resource)); - } - - protected Analysis analyzeString(String javaCode) { - return analyze(StaticJavaParser.parse(javaCode)); - } - - private Analysis analyze(CompilationUnit compilationUnit) { - var analysis = new Analysis(); - getAnalyzer().analyze(List.of(compilationUnit), analysis); - return analysis; - } - - private T getAnalyzer() { - try { - return this.analyzerClass.getConstructor().newInstance(); - } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) { - throw new AssertionError("Unable to create instance of " + this.analyzerClass.getName(), ex); - } - } -} diff --git a/src/test/java/analyzer/OutputWriterTest.java b/src/test/java/analyzer/OutputWriterTest.java index 04175d1c..4f9ce049 100644 --- a/src/test/java/analyzer/OutputWriterTest.java +++ b/src/test/java/analyzer/OutputWriterTest.java @@ -29,10 +29,10 @@ public void serializeAnalysis() throws IOException { analysis.addComment(new TestComment("key_only")); analysis.addComment(new TestComment("key_and_single_param", Map.of("param1", "value1"))); analysis.addComment(new TestComment("key_and_multiple_params", Map.of("param1", "value1", "param2", "value2"))); - analysis.addComment(new TestComment("celebratory", CommentType.CELEBRATORY)); - analysis.addComment(new TestComment("actionable", CommentType.ACTIONABLE)); - analysis.addComment(new TestComment("essential", CommentType.ESSENTIAL)); - analysis.addComment(new TestComment("informative", CommentType.INFORMATIVE)); + analysis.addComment(new TestComment("celebratory", Comment.Type.CELEBRATORY)); + analysis.addComment(new TestComment("actionable", Comment.Type.ACTIONABLE)); + analysis.addComment(new TestComment("essential", Comment.Type.ESSENTIAL)); + analysis.addComment(new TestComment("informative", Comment.Type.INFORMATIVE)); analysis.setSummary("Lorum Ipsum"); outputWriter.write(analysis); @@ -103,10 +103,10 @@ public void serializeEmptyAnalysis() throws IOException { private static class TestComment extends Comment { private final String key; - private final CommentType type; + private final Type type; private final Map parameters; - private TestComment(String key, CommentType type, Map parameters) { + private TestComment(String key, Type type, Map parameters) { this.key = Objects.requireNonNull(key); this.type = type; this.parameters = Objects.requireNonNull(parameters); @@ -116,7 +116,7 @@ private TestComment(String key) { this(key, null, Map.of()); } - private TestComment(String key, CommentType type) { + private TestComment(String key, Type type) { this(key, type, Map.of()); } @@ -130,7 +130,7 @@ public String getKey() { } @Override - public CommentType getType() { + public Type getType() { return this.type; } diff --git a/src/test/java/analyzer/SolutionFromResourceFiles.java b/src/test/java/analyzer/SolutionFromResourceFiles.java new file mode 100644 index 00000000..53dc90a6 --- /dev/null +++ b/src/test/java/analyzer/SolutionFromResourceFiles.java @@ -0,0 +1,37 @@ +package analyzer; + +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; + +import java.util.ArrayList; +import java.util.List; + +public class SolutionFromResourceFiles implements Solution { + private final String slug; + private final List compilationUnits; + + public SolutionFromResourceFiles(String slug, String resourceFileName, String... moreResourceFileNames) { + this.slug = slug; + this.compilationUnits = new ArrayList<>(); + + compilationUnits.add(parseResourceFile(resourceFileName)); + for (String fileName : moreResourceFileNames) { + compilationUnits.add(parseResourceFile(fileName)); + } + } + + private static CompilationUnit parseResourceFile(String fileName) { + var inputStream = SolutionFromResourceFiles.class.getResourceAsStream(fileName); + return StaticJavaParser.parse(inputStream); + } + + @Override + public String getSlug() { + return this.slug; + } + + @Override + public List getCompilationUnits() { + return List.copyOf(this.compilationUnits); + } +} diff --git a/src/test/java/analyzer/SolutionFromString.java b/src/test/java/analyzer/SolutionFromString.java new file mode 100644 index 00000000..d2df0dd4 --- /dev/null +++ b/src/test/java/analyzer/SolutionFromString.java @@ -0,0 +1,26 @@ +package analyzer; + +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; + +import java.util.List; + +public class SolutionFromString implements Solution { + private final String slug; + private final CompilationUnit compilationUnit; + + public SolutionFromString(String slug, String code) { + this.slug = slug; + this.compilationUnit = StaticJavaParser.parse(code); + } + + @Override + public String getSlug() { + return this.slug; + } + + @Override + public List getCompilationUnits() { + return List.of(this.compilationUnit); + } +} diff --git a/src/test/java/analyzer/exercises/GlobalAnalyzerTest.java b/src/test/java/analyzer/exercises/GlobalAnalyzerTest.java index 610e200d..3f0f1d69 100644 --- a/src/test/java/analyzer/exercises/GlobalAnalyzerTest.java +++ b/src/test/java/analyzer/exercises/GlobalAnalyzerTest.java @@ -1,6 +1,7 @@ package analyzer.exercises; -import analyzer.AnalyzerTest; +import analyzer.AnalyzerRoot; +import analyzer.SolutionFromString; import analyzer.comments.AvoidPrintStatements; import analyzer.comments.DoNotUseMainMethod; import org.junit.jupiter.params.ParameterizedTest; @@ -10,15 +11,13 @@ import static org.assertj.core.api.Assertions.assertThat; -public class GlobalAnalyzerTest extends AnalyzerTest { - public GlobalAnalyzerTest() { - super(GlobalAnalyzer.class); - } +public class GlobalAnalyzerTest { @MethodSource @ParameterizedTest - public void solutionsWithMainMethod(String solution) { - var actual = analyzeString(solution); + public void solutionsWithMainMethod(String code) { + var solution = new SolutionFromString("any-exercise", code); + var actual = AnalyzerRoot.analyze(solution); assertThat(actual.getComments()).contains(new DoNotUseMainMethod()); } @@ -41,8 +40,9 @@ public static void main(String[] args) {} @MethodSource @ParameterizedTest - public void solutionsWithPrintStatements(String solution) { - var actual = analyzeString(solution); + public void solutionsWithPrintStatements(String code) { + var solution = new SolutionFromString("any-exercise", code); + var actual = AnalyzerRoot.analyze(solution); assertThat(actual.getComments()).contains(new AvoidPrintStatements()); } diff --git a/src/test/java/analyzer/exercises/hamming/HammingAnalyzerTest.java b/src/test/java/analyzer/exercises/hamming/HammingAnalyzerTest.java index 31933f5b..8be0033a 100644 --- a/src/test/java/analyzer/exercises/hamming/HammingAnalyzerTest.java +++ b/src/test/java/analyzer/exercises/hamming/HammingAnalyzerTest.java @@ -1,11 +1,10 @@ package analyzer.exercises.hamming; -import analyzer.AnalyzerTest; +import analyzer.AnalyzerRoot; import analyzer.Comment; +import analyzer.SolutionFromResourceFiles; import analyzer.comments.ConstructorTooLong; import analyzer.comments.MethodTooLong; -import analyzer.comments.UseProperClassName; -import analyzer.comments.UseProperMethodName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -14,15 +13,10 @@ import static org.assertj.core.api.Assertions.assertThat; -public class HammingAnalyzerTest extends AnalyzerTest { - public HammingAnalyzerTest() { - super(HammingAnalyzer.class); - } +public class HammingAnalyzerTest { private static Stream testCases() { return Stream.of( - Arguments.of("NoHammingClass.java.txt", new Comment[]{new UseProperClassName("Hamming")}), - Arguments.of("NoGetHammingDistanceMethod.java.txt", new Comment[]{new UseProperMethodName("getHammingDistance")}), Arguments.of("NoConstructor.java.txt", new Comment[]{new MustUseConstructor()}), Arguments.of("NoConditionalInConstructor.java.txt", new Comment[]{new MustUseConditionalLogicInConstructor()}), Arguments.of("DoesNotThrowInConstructor.java.txt", new Comment[]{new MustThrowInConstructor()}), @@ -44,9 +38,10 @@ private static Stream testCases() { @MethodSource("testCases") @ParameterizedTest(name = "{0}") public void testCommentsOnSolution(String solutionFile, Comment... expectedComments) { - var actual = analyzeResourceFile(getResourceFileName(solutionFile)); + var solution = new SolutionFromResourceFiles("hamming", getResourceFileName(solutionFile)); + var analysis = AnalyzerRoot.analyze(solution); - assertThat(actual.getComments()).containsExactly(expectedComments); + assertThat(analysis.getComments()).contains(expectedComments); } private static String getResourceFileName(String testFileName) { diff --git a/src/test/java/analyzer/exercises/lasagna/LasagnaAnalyzerTest.java b/src/test/java/analyzer/exercises/lasagna/LasagnaAnalyzerTest.java index 59392a44..09685efb 100644 --- a/src/test/java/analyzer/exercises/lasagna/LasagnaAnalyzerTest.java +++ b/src/test/java/analyzer/exercises/lasagna/LasagnaAnalyzerTest.java @@ -1,7 +1,8 @@ package analyzer.exercises.lasagna; -import analyzer.AnalyzerTest; +import analyzer.AnalyzerRoot; import analyzer.Comment; +import analyzer.SolutionFromResourceFiles; import analyzer.comments.ExemplarSolution; import analyzer.comments.RemoveTodoComments; import org.junit.jupiter.params.ParameterizedTest; @@ -13,10 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class LasagnaAnalyzerTest extends AnalyzerTest { - public LasagnaAnalyzerTest() { - super(LasagnaAnalyzer.class); - } +public class LasagnaAnalyzerTest { private static Stream testCases() { return Stream.of( @@ -38,7 +36,8 @@ private static Stream testCases() { @ParameterizedTest(name = "{0}") @MethodSource("testCases") public void testCommentsOnSolution(String filename, List expectedComments) { - var analysis = analyzeResourceFile("/analyzer/exercises/lasagna/" + filename); - assertThat(analysis.getComments()).containsExactlyInAnyOrder(expectedComments.toArray(Comment[]::new)); + var solution = new SolutionFromResourceFiles("lasagna", "/analyzer/exercises/lasagna/" + filename); + var analysis = AnalyzerRoot.analyze(solution); + assertThat(analysis.getComments()).contains(expectedComments.toArray(Comment[]::new)); } } diff --git a/src/test/java/analyzer/exercises/leap/LeapAnalyzerTest.java b/src/test/java/analyzer/exercises/leap/LeapAnalyzerTest.java index 6f2883b5..b25412c0 100644 --- a/src/test/java/analyzer/exercises/leap/LeapAnalyzerTest.java +++ b/src/test/java/analyzer/exercises/leap/LeapAnalyzerTest.java @@ -1,7 +1,8 @@ package analyzer.exercises.leap; -import analyzer.AnalyzerTest; +import analyzer.AnalyzerRoot; import analyzer.Comment; +import analyzer.SolutionFromResourceFiles; import analyzer.comments.AvoidHardCodedTestCases; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -12,16 +13,12 @@ import static org.assertj.core.api.Assertions.assertThat; -public class LeapAnalyzerTest extends AnalyzerTest { - private static final String RESOURCE_LOCATION = "/analyzer/exercises/leap/"; - - public LeapAnalyzerTest() { - super(LeapAnalyzer.class); - } +public class LeapAnalyzerTest { @Test public void optimalSolution() { - var analysis = analyzeResourceFile(RESOURCE_LOCATION + "OptimalSolution.java"); + var solution = new SolutionFromResourceFiles("leap", getResourceFileName("OptimalSolution.java")); + var analysis = AnalyzerRoot.analyze(solution); assertThat(analysis.getComments()).isEmpty(); } @@ -39,7 +36,12 @@ private static Stream testCases() { @ParameterizedTest(name = "{0}") @MethodSource("testCases") public void testCommentsOnSolution(String filename, Comment expectedComment) { - var analysis = analyzeResourceFile(RESOURCE_LOCATION + filename); + var solution = new SolutionFromResourceFiles("leap", getResourceFileName(filename)); + var analysis = AnalyzerRoot.analyze(solution); assertThat(analysis.getComments()).contains(expectedComment); } + + private static String getResourceFileName(String testFileName) { + return "/analyzer/exercises/leap/" + testFileName; + } } diff --git a/src/test/java/analyzer/exercises/twofer/TwoferAnalyzerTest.java b/src/test/java/analyzer/exercises/twofer/TwoferAnalyzerTest.java index fc568b6d..e0b3fd35 100644 --- a/src/test/java/analyzer/exercises/twofer/TwoferAnalyzerTest.java +++ b/src/test/java/analyzer/exercises/twofer/TwoferAnalyzerTest.java @@ -1,10 +1,9 @@ package analyzer.exercises.twofer; -import analyzer.AnalyzerTest; +import analyzer.AnalyzerRoot; import analyzer.Comment; +import analyzer.SolutionFromResourceFiles; import analyzer.comments.AvoidHardCodedTestCases; -import analyzer.comments.UseProperClassName; -import analyzer.comments.UseProperMethodName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -13,16 +12,10 @@ import static org.assertj.core.api.Assertions.assertThat; -public class TwoferAnalyzerTest extends AnalyzerTest { - - public TwoferAnalyzerTest() { - super(TwoferAnalyzer.class); - } +public class TwoferAnalyzerTest { private static Stream testCases() { return Stream.of( - Arguments.of("NoTwoferClass.java.txt", new Comment[]{new UseProperClassName("Twofer")}), - Arguments.of("NoTwoferMethod.java.txt", new Comment[]{new UseProperMethodName("twofer")}), Arguments.of("UsesLambda.java.txt", new Comment[0]), Arguments.of("UsesLoop.java.txt", new Comment[0]), Arguments.of("HardCodedTestCases.java.txt", new Comment[]{new AvoidHardCodedTestCases()}), @@ -37,9 +30,10 @@ private static Stream testCases() { @MethodSource("testCases") @ParameterizedTest(name = "{0}") public void testCommentsOnSolution(String solutionFile, Comment... expectedComments) { - var actual = analyzeResourceFile(getResourceFileName(solutionFile)); + var solution = new SolutionFromResourceFiles("two-fer", getResourceFileName(solutionFile)); + var actual = AnalyzerRoot.analyze(solution); - assertThat(actual.getComments()).containsExactly(expectedComments); + assertThat(actual.getComments()).contains(expectedComments); } private static String getResourceFileName(String testFileName) { diff --git a/src/test/resources/analyzer/exercises/hamming/NoGetHammingDistanceMethod.java.txt b/src/test/resources/analyzer/exercises/hamming/NoGetHammingDistanceMethod.java.txt deleted file mode 100644 index 56f8cbec..00000000 --- a/src/test/resources/analyzer/exercises/hamming/NoGetHammingDistanceMethod.java.txt +++ /dev/null @@ -1,6 +0,0 @@ -package analyzer.exercises.hamming; - -/** There must be a getHammingDistance method. */ -public class Hamming { - public void notGetHammingDistanceMethod() {} -} \ No newline at end of file diff --git a/src/test/resources/analyzer/exercises/hamming/NoHammingClass.java.txt b/src/test/resources/analyzer/exercises/hamming/NoHammingClass.java.txt deleted file mode 100644 index a2206578..00000000 --- a/src/test/resources/analyzer/exercises/hamming/NoHammingClass.java.txt +++ /dev/null @@ -1,4 +0,0 @@ -package analyzer.exercises.hamming; - -/** There must be a Hamming class. */ -public class NoHammingClass {} \ No newline at end of file diff --git a/src/test/resources/analyzer/exercises/twofer/NoTwoferClass.java.txt b/src/test/resources/analyzer/exercises/twofer/NoTwoferClass.java.txt deleted file mode 100644 index 583d314e..00000000 --- a/src/test/resources/analyzer/exercises/twofer/NoTwoferClass.java.txt +++ /dev/null @@ -1,4 +0,0 @@ -package analyzer.exercises.twofer; - -/** Empty class for testing that the class must be named correctly. */ -public class NoTwoferClass {} \ No newline at end of file diff --git a/src/test/resources/analyzer/exercises/twofer/NoTwoferMethod.java.txt b/src/test/resources/analyzer/exercises/twofer/NoTwoferMethod.java.txt deleted file mode 100644 index 698a7265..00000000 --- a/src/test/resources/analyzer/exercises/twofer/NoTwoferMethod.java.txt +++ /dev/null @@ -1,6 +0,0 @@ -package analyzer.exercises.twofer; - -/** Empty class for testing that there must be a twofer method. */ -public class Twofer { - public void notTwoferMethod() {} -} \ No newline at end of file