From c7c64e1110a5a06179e45eb4f71389402bd00d5c Mon Sep 17 00:00:00 2001 From: Sander Ploegsma Date: Fri, 19 Jan 2024 13:01:25 +0100 Subject: [PATCH] Refactor analyzer (#82) * Refactor JSON writing behavior * Preserve order of added comments and tags * Rename Exercise to Analyzer * Add javadocs referencing the Exercism documentation * Refactor Analyzer base class into interface * Rename JsonSerializerTest.java to OutputWriterTest.java * Remove unused general comments The `FailedParse` comment was removed because it doesn't make much sense to have the analyzer comment on a submission that does not even compile. The `FileNotFound` comment was removed because the analyzer now analyzes all files in the source root, so there is no need to search for a specific file anymore. * Add golden test verifying that the analyzer doesn't crash on unknown exercises * Add .gitattributes --- .gitattributes | 3 + build.gradle | 1 - src/main/java/analyzer/Analysis.java | 38 +++ src/main/java/analyzer/Analyzer.java | 9 + src/main/java/analyzer/AnalyzerRoot.java | 30 ++ src/main/java/analyzer/Comment.java | 46 +++ src/main/java/analyzer/CommentType.java | 11 + src/main/java/analyzer/Main.java | 54 ++- src/main/java/analyzer/OutputWriter.java | 67 ++++ .../comments/AvoidHardCodedTestCases.java | 13 + .../analyzer/comments/ConstructorTooLong.java | 32 ++ .../java/analyzer/comments/MethodTooLong.java | 32 ++ .../analyzer/comments/UseProperClassName.java | 26 ++ .../comments/UseProperMethodName.java | 26 ++ src/main/java/analyzer/exercises/Comment.java | 5 - .../java/analyzer/exercises/Exercise.java | 144 -------- .../analyzer/exercises/GeneralComment.java | 21 -- src/main/java/analyzer/exercises/Params.java | 43 --- .../hamming/AvoidCharacterLiterals.java | 13 + .../CalculateDistanceInConstructor.java | 13 + .../analyzer/exercises/hamming/Hamming.java | 133 -------- .../exercises/hamming/HammingAnalyzer.java | 86 +++++ .../exercises/hamming/HammingComment.java | 20 -- .../hamming/MustCalculateHammingDistance.java | 13 + .../hamming/MustThrowInConstructor.java | 13 + .../MustUseConditionalLogicInConstructor.java | 13 + .../exercises/hamming/MustUseConstructor.java | 13 + .../MustUseStringCharAtOrCodePointAt.java | 13 + .../ShouldUseStreamFilterAndCount.java | 13 + .../hamming/ShouldUseStringIsEmpty.java | 13 + .../exercises/twofer/AvoidStringFormat.java | 13 + .../analyzer/exercises/twofer/Twofer.java | 76 ----- .../exercises/twofer/TwoferAnalyzer.java | 42 +++ .../exercises/twofer/TwoferComment.java | 15 - .../exercises/twofer/UseConditionalLogic.java | 13 + .../exercises/twofer/UseOneReturn.java | 13 + .../exercises/twofer/UseTernaryOperator.java | 13 + .../java/analyzer/ExerciseAnalyzerTest.java | 17 + src/test/java/analyzer/OutputWriterTest.java | 133 ++++++++ .../hamming/HammingAnalyzerTest.java | 57 ++++ .../exercises/hamming/HammingTest.java | 312 ------------------ .../exercises/twofer/TwoferAnalyzerTest.java | 50 +++ .../analyzer/exercises/twofer/TwoferTest.java | 155 --------- tests/hamming/expected_analysis.json | 5 +- tests/unknown-exercise/.meta/config.json | 22 ++ tests/unknown-exercise/expected_analysis.json | 1 + tests/unknown-exercise/expected_tags.json | 1 + .../src/main/java/UnknownExercise.java | 5 + 48 files changed, 956 insertions(+), 944 deletions(-) create mode 100644 .gitattributes create mode 100644 src/main/java/analyzer/Analysis.java create mode 100644 src/main/java/analyzer/Analyzer.java create mode 100644 src/main/java/analyzer/AnalyzerRoot.java create mode 100644 src/main/java/analyzer/Comment.java create mode 100644 src/main/java/analyzer/CommentType.java create mode 100644 src/main/java/analyzer/OutputWriter.java create mode 100644 src/main/java/analyzer/comments/AvoidHardCodedTestCases.java create mode 100644 src/main/java/analyzer/comments/ConstructorTooLong.java create mode 100644 src/main/java/analyzer/comments/MethodTooLong.java create mode 100644 src/main/java/analyzer/comments/UseProperClassName.java create mode 100644 src/main/java/analyzer/comments/UseProperMethodName.java delete mode 100644 src/main/java/analyzer/exercises/Comment.java delete mode 100644 src/main/java/analyzer/exercises/Exercise.java delete mode 100644 src/main/java/analyzer/exercises/GeneralComment.java delete mode 100644 src/main/java/analyzer/exercises/Params.java create mode 100644 src/main/java/analyzer/exercises/hamming/AvoidCharacterLiterals.java create mode 100644 src/main/java/analyzer/exercises/hamming/CalculateDistanceInConstructor.java delete mode 100644 src/main/java/analyzer/exercises/hamming/Hamming.java create mode 100644 src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java delete mode 100644 src/main/java/analyzer/exercises/hamming/HammingComment.java create mode 100644 src/main/java/analyzer/exercises/hamming/MustCalculateHammingDistance.java create mode 100644 src/main/java/analyzer/exercises/hamming/MustThrowInConstructor.java create mode 100644 src/main/java/analyzer/exercises/hamming/MustUseConditionalLogicInConstructor.java create mode 100644 src/main/java/analyzer/exercises/hamming/MustUseConstructor.java create mode 100644 src/main/java/analyzer/exercises/hamming/MustUseStringCharAtOrCodePointAt.java create mode 100644 src/main/java/analyzer/exercises/hamming/ShouldUseStreamFilterAndCount.java create mode 100644 src/main/java/analyzer/exercises/hamming/ShouldUseStringIsEmpty.java create mode 100644 src/main/java/analyzer/exercises/twofer/AvoidStringFormat.java delete mode 100644 src/main/java/analyzer/exercises/twofer/Twofer.java create mode 100644 src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java delete mode 100644 src/main/java/analyzer/exercises/twofer/TwoferComment.java create mode 100644 src/main/java/analyzer/exercises/twofer/UseConditionalLogic.java create mode 100644 src/main/java/analyzer/exercises/twofer/UseOneReturn.java create mode 100644 src/main/java/analyzer/exercises/twofer/UseTernaryOperator.java create mode 100644 src/test/java/analyzer/ExerciseAnalyzerTest.java create mode 100644 src/test/java/analyzer/OutputWriterTest.java create mode 100644 src/test/java/analyzer/exercises/hamming/HammingAnalyzerTest.java delete mode 100644 src/test/java/analyzer/exercises/hamming/HammingTest.java create mode 100644 src/test/java/analyzer/exercises/twofer/TwoferAnalyzerTest.java delete mode 100644 src/test/java/analyzer/exercises/twofer/TwoferTest.java create mode 100644 tests/unknown-exercise/.meta/config.json create mode 100644 tests/unknown-exercise/expected_analysis.json create mode 100644 tests/unknown-exercise/expected_tags.json create mode 100644 tests/unknown-exercise/src/main/java/UnknownExercise.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..282705fb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto + +*.bat text eol=crlf diff --git a/build.gradle b/build.gradle index a7efaec6..a88d0123 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,6 @@ repositories { dependencies { implementation "org.json:json:20231013" implementation "com.github.javaparser:javaparser-core:3.25.7" - implementation "com.google.guava:guava:33.0.0-jre" testImplementation platform("org.junit:junit-bom:5.10.0") testImplementation "org.junit.jupiter:junit-jupiter" diff --git a/src/main/java/analyzer/Analysis.java b/src/main/java/analyzer/Analysis.java new file mode 100644 index 00000000..50094c42 --- /dev/null +++ b/src/main/java/analyzer/Analysis.java @@ -0,0 +1,38 @@ +package analyzer; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * @see The analyzer interface in the Exercism documentation + */ +public class Analysis { + private String summary; + private final Set comments = new LinkedHashSet<>(); + private final Set tags = new LinkedHashSet<>(); + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public List getComments() { + return List.copyOf(comments); + } + + public List getTags() { + return List.copyOf(tags); + } + + public void addComment(Comment comment) { + comments.add(comment); + } + + public void addTag(String tag) { + tags.add(tag); + } +} diff --git a/src/main/java/analyzer/Analyzer.java b/src/main/java/analyzer/Analyzer.java new file mode 100644 index 00000000..b4e5c386 --- /dev/null +++ b/src/main/java/analyzer/Analyzer.java @@ -0,0 +1,9 @@ +package analyzer; + +import com.github.javaparser.ast.CompilationUnit; + +import java.util.List; + +public interface Analyzer { + void analyze(List compilationUnits, Analysis analysis); +} diff --git a/src/main/java/analyzer/AnalyzerRoot.java b/src/main/java/analyzer/AnalyzerRoot.java new file mode 100644 index 00000000..1ff8a654 --- /dev/null +++ b/src/main/java/analyzer/AnalyzerRoot.java @@ -0,0 +1,30 @@ +package analyzer; + +import analyzer.exercises.hamming.HammingAnalyzer; +import analyzer.exercises.twofer.TwoferAnalyzer; +import com.github.javaparser.ast.CompilationUnit; + +import java.util.ArrayList; +import java.util.List; + +public class AnalyzerRoot { + + public static Analysis analyze(String slug, List compilationUnits) { + var analysis = new Analysis(); + for (Analyzer analyzer : createAnalyzers(slug)) { + analyzer.analyze(compilationUnits, analysis); + } + return analysis; + } + + private static List createAnalyzers(String slug) { + var analyzers = new ArrayList(); + + switch (slug) { + case "hamming" -> analyzers.add(new HammingAnalyzer()); + case "two-fer" -> analyzers.add(new TwoferAnalyzer()); + } + + return List.copyOf(analyzers); + } +} diff --git a/src/main/java/analyzer/Comment.java b/src/main/java/analyzer/Comment.java new file mode 100644 index 00000000..43bcd655 --- /dev/null +++ b/src/main/java/analyzer/Comment.java @@ -0,0 +1,46 @@ +package analyzer; + +import java.util.Map; +import java.util.Objects; + +/** + * @see The analyzer interface in the Exercism documentation + */ +public abstract class Comment { + + public abstract String getKey(); + + public Map getParameters() { + return Map.of(); + } + + public CommentType getType() { + return null; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof Comment other) && equals(other); + } + + public boolean equals(Comment other) { + if (!getKey().equals(other.getKey()) || getType() != other.getType()) { + return false; + } + + var params = this.getParameters().entrySet(); + var otherParams = other.getParameters().entrySet(); + + return params.containsAll(otherParams) && otherParams.containsAll(params); + } + + @Override + public int hashCode() { + return Objects.hash(getKey(), getType(), getParameters()); + } + + @Override + public String toString() { + return String.format("Comment{key=%s,params=%s,type=%s}", getKey(), getParameters(), getType()); + } +} diff --git a/src/main/java/analyzer/CommentType.java b/src/main/java/analyzer/CommentType.java new file mode 100644 index 00000000..9d50a567 --- /dev/null +++ b/src/main/java/analyzer/CommentType.java @@ -0,0 +1,11 @@ +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 index b2b7afe5..911c6cc7 100644 --- a/src/main/java/analyzer/Main.java +++ b/src/main/java/analyzer/Main.java @@ -1,17 +1,23 @@ package analyzer; -import analyzer.exercises.Exercise; -import analyzer.exercises.twofer.Twofer; -import analyzer.exercises.hamming.Hamming; +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(); } - public static void main(String... args) { + private static Options validateOptions(String... args) { if (args.length < 3) { System.err.println("Invalid arguments. Usage: java-analyzer "); System.exit(-1); @@ -30,21 +36,33 @@ public static void main(String... args) { System.exit(-1); } - Exercise ex = null; - switch (slug) { - case "two-fer": - ex = new Twofer(inputDirectory, outputDirectory); - break; - case "hamming": - ex = new Hamming(inputDirectory, outputDirectory); - break; - default: - System.err.println("Exercise not found"); - 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()); } - ex.parse(); - ex.writeAnalysisToFile(); - System.out.println("Analysis completed successfully"); + 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 new file mode 100644 index 00000000..635714d2 --- /dev/null +++ b/src/main/java/analyzer/OutputWriter.java @@ -0,0 +1,67 @@ +package analyzer; + +import org.json.JSONObject; + +import java.io.IOException; +import java.io.Writer; + +/** + * @see The analyzer interface in the Exercism documentation + */ +public class OutputWriter { + private static final int JSON_INDENTATION = 2; + + private final Writer analysisWriter; + private final Writer tagsWriter; + + public OutputWriter(Writer analysisWriter, Writer tagsWriter) { + this.analysisWriter = analysisWriter; + this.tagsWriter = tagsWriter; + } + + public void write(Analysis analysis) throws IOException { + writeAnalysis(analysis); + writeTags(analysis); + } + + private void writeAnalysis(Analysis analysis) throws IOException { + var json = new JSONObject(); + + if (analysis.getSummary() != null) { + json.put("summary", analysis.getSummary()); + } + + for (Comment comment : analysis.getComments()) { + json.append("comments", serialize(comment)); + } + + this.analysisWriter.write(json.toString(JSON_INDENTATION)); + } + + private void writeTags(Analysis analysis) throws IOException { + var json = new JSONObject(); + for (String tag : analysis.getTags()) { + json.append("tags", tag); + } + + this.tagsWriter.write(json.toString(JSON_INDENTATION)); + } + + private static JSONObject serialize(Comment comment) { + var json = new JSONObject(); + json.put("comment", comment.getKey()); + + if (comment.getType() != null) { + json.put("type", comment.getType().name().toLowerCase()); + } + + if (comment.getParameters().isEmpty()) { + return json; + } + + var paramsJson = new JSONObject(); + comment.getParameters().forEach(paramsJson::put); + json.put("params", paramsJson); + return json; + } +} diff --git a/src/main/java/analyzer/comments/AvoidHardCodedTestCases.java b/src/main/java/analyzer/comments/AvoidHardCodedTestCases.java new file mode 100644 index 00000000..7733884b --- /dev/null +++ b/src/main/java/analyzer/comments/AvoidHardCodedTestCases.java @@ -0,0 +1,13 @@ +package analyzer.comments; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +public class AvoidHardCodedTestCases extends Comment { + @Override + public String getKey() { + return "java.general.avoid_hard_coded_test_cases"; + } +} diff --git a/src/main/java/analyzer/comments/ConstructorTooLong.java b/src/main/java/analyzer/comments/ConstructorTooLong.java new file mode 100644 index 00000000..d3b1e52d --- /dev/null +++ b/src/main/java/analyzer/comments/ConstructorTooLong.java @@ -0,0 +1,32 @@ +package analyzer.comments; + +import analyzer.Comment; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * @see Markdown Template + */ +public class ConstructorTooLong extends Comment { + private final Collection constructorNames; + + public ConstructorTooLong(Collection constructorNames) { + this.constructorNames = constructorNames; + } + + public ConstructorTooLong(String constructorName) { + this(List.of(constructorName)); + } + + @Override + public String getKey() { + return "java.general.constructor_too_long"; + } + + @Override + public Map getParameters() { + return Map.of("constructorNames", String.join(", ", this.constructorNames)); + } +} diff --git a/src/main/java/analyzer/comments/MethodTooLong.java b/src/main/java/analyzer/comments/MethodTooLong.java new file mode 100644 index 00000000..7e734e53 --- /dev/null +++ b/src/main/java/analyzer/comments/MethodTooLong.java @@ -0,0 +1,32 @@ +package analyzer.comments; + +import analyzer.Comment; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * @see Markdown Template + */ +public class MethodTooLong extends Comment { + private final Collection methodNames; + + public MethodTooLong(Collection methodNames) { + this.methodNames = methodNames; + } + + public MethodTooLong(String methodName) { + this(List.of(methodName)); + } + + @Override + public String getKey() { + return "java.general.method_too_long"; + } + + @Override + public Map getParameters() { + return Map.of("methodNames", String.join(", ", this.methodNames)); + } +} diff --git a/src/main/java/analyzer/comments/UseProperClassName.java b/src/main/java/analyzer/comments/UseProperClassName.java new file mode 100644 index 00000000..59cbc962 --- /dev/null +++ b/src/main/java/analyzer/comments/UseProperClassName.java @@ -0,0 +1,26 @@ +package analyzer.comments; + +import analyzer.Comment; + +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); + } +} diff --git a/src/main/java/analyzer/comments/UseProperMethodName.java b/src/main/java/analyzer/comments/UseProperMethodName.java new file mode 100644 index 00000000..2b06145f --- /dev/null +++ b/src/main/java/analyzer/comments/UseProperMethodName.java @@ -0,0 +1,26 @@ +package analyzer.comments; + +import analyzer.Comment; + +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); + } +} diff --git a/src/main/java/analyzer/exercises/Comment.java b/src/main/java/analyzer/exercises/Comment.java deleted file mode 100644 index 24627f27..00000000 --- a/src/main/java/analyzer/exercises/Comment.java +++ /dev/null @@ -1,5 +0,0 @@ -package analyzer.exercises; - -public interface Comment { - String toJson(); -} \ No newline at end of file diff --git a/src/main/java/analyzer/exercises/Exercise.java b/src/main/java/analyzer/exercises/Exercise.java deleted file mode 100644 index 08f9fef0..00000000 --- a/src/main/java/analyzer/exercises/Exercise.java +++ /dev/null @@ -1,144 +0,0 @@ -package analyzer.exercises; - -import com.github.javaparser.JavaParser; -import com.github.javaparser.ParseProblemException; -import com.github.javaparser.ast.CompilationUnit; -import org.json.JSONObject; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileWriter; -import java.io.IOException; - -public abstract class Exercise { - private static final String COMMENTS = "comments"; - private static final String COMMENT = "comment"; - private static final String PARAMS = "params"; - private static final FileWriter NO_FILE_WRITER = null; - - private CompilationUnit compilationUnit; - - private final JSONObject analysis = new JSONObject(); - private final JSONObject tags = new JSONObject(); - private final FileWriter analysisFileWriter; - private final FileWriter tagsFileWriter; - - public enum WriteAnalysisToFile {YES, NO} - - protected Exercise(String directory, - String solutionFile, - String outputDirectory, - WriteAnalysisToFile writeAnalysisToFile) { - this(getSolutionFile(directory, solutionFile), - getAnalysisFileWriter(outputDirectory, writeAnalysisToFile), - getTagsFileWriter(directory, writeAnalysisToFile)); - } - - protected Exercise(File solutionFile) { - this(solutionFile, NO_FILE_WRITER, NO_FILE_WRITER); - } - - private Exercise(File solutionFile, FileWriter analysisFileWriter, FileWriter tagsFileWriter) { - this.analysisFileWriter = analysisFileWriter; - this.tagsFileWriter = tagsFileWriter; - - try { - this.compilationUnit = new JavaParser().parse(solutionFile).getResult().get(); - } catch (ParseProblemException e) { - addComment(GeneralComment.FAILED_PARSE); - } catch (FileNotFoundException e) { - addComment( - GeneralComment.FILE_NOT_FOUND, - Params.newBuilder().addParam("solutionFile", solutionFile.getName()).build()); - } - } - - /** - * @deprecated The output file should no longer be written in the input file directory - */ - @Deprecated - protected Exercise(String directory, String solutionFile) { - this(directory, solutionFile, WriteAnalysisToFile.YES); - } - - /** - * @deprecated The output file should no longer be written in the input file directory - */ - @Deprecated - protected Exercise(String directory, - String solutionFile, - WriteAnalysisToFile writeAnalysisToFile) { - this(getSolutionFile(directory, solutionFile), - getAnalysisFileWriter(directory, writeAnalysisToFile), - getTagsFileWriter(directory, writeAnalysisToFile)); - } - - private static File getSolutionFile(String directory, String solutionFile) { - return new File(directory + "src/main/java/" + solutionFile); - } - - private static FileWriter getAnalysisFileWriter(String directory, WriteAnalysisToFile writeAnalysisToFile) { - return getFileWriter(directory, writeAnalysisToFile, "analysis.json"); - } - - private static FileWriter getTagsFileWriter(String directory, WriteAnalysisToFile writeAnalysisToFile) { - return getFileWriter(directory, writeAnalysisToFile, "tags.json"); - } - - private static FileWriter getFileWriter( - String directory, WriteAnalysisToFile writeAnalysisToFile, String filename) { - if (writeAnalysisToFile == WriteAnalysisToFile.NO) { - return null; - } - try { - return new FileWriter(directory + filename); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - - public final void parse() { - if (compilationUnit == null) { - return; - } - parse(compilationUnit); - } - - abstract public void parse(CompilationUnit compilationUnit); - - protected void addComment(Comment comment) { - addComment(comment, Params.EMPTY); - } - - protected void addComment(Comment comment, Params params) { - if (params.isEmpty()) { - this.analysis.append(COMMENTS, comment.toJson()); - return; - } - this.analysis.append( - COMMENTS, - new JSONObject() - .put(COMMENT, comment.toJson()) - .put(PARAMS, params.toJson())); - } - - public JSONObject getAnalysis() { - return analysis; - } - - public void writeAnalysisToFile() { - if (analysisFileWriter == null) { - return; - } - - try { - analysisFileWriter.write(analysis.toString()); - analysisFileWriter.flush(); - tagsFileWriter.write(tags.toString()); - tagsFileWriter.flush(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/analyzer/exercises/GeneralComment.java b/src/main/java/analyzer/exercises/GeneralComment.java deleted file mode 100644 index cab47017..00000000 --- a/src/main/java/analyzer/exercises/GeneralComment.java +++ /dev/null @@ -1,21 +0,0 @@ -package analyzer.exercises; - -public enum GeneralComment implements Comment { - AVOID_HARD_CODED_TEST_CASES, - FAILED_PARSE, - FILE_NOT_FOUND, - USE_PROPER_CLASS_NAME, - USE_PROPER_METHOD_NAME, - CONSTRUCTOR_TOO_LONG, - METHOD_TOO_LONG; - - public static String CLASS_NAME = "className"; - public static String METHOD_NAME = "methodName"; - public static String CONSTRUCTOR_NAMES = "constructorNames"; - public static String METHOD_NAMES = "methodNames"; - - @Override - public String toJson() { - return "java.general." + name().toLowerCase(); - } -} \ No newline at end of file diff --git a/src/main/java/analyzer/exercises/Params.java b/src/main/java/analyzer/exercises/Params.java deleted file mode 100644 index 219b1603..00000000 --- a/src/main/java/analyzer/exercises/Params.java +++ /dev/null @@ -1,43 +0,0 @@ -package analyzer.exercises; - -import com.google.common.collect.ImmutableMap; -import java.util.Map; -import org.json.JSONObject; - -public final class Params { - public static final Params EMPTY = newBuilder().build(); - - private final Map params; - - private Params(Map params) { - this.params = params; - } - - public boolean isEmpty() { - return params.isEmpty(); - } - - public JSONObject toJson() { - JSONObject json = new JSONObject(); - params.forEach((param, value) -> json.put(param, value)); - return json; - } - - public static Builder newBuilder() { - return new Builder(); - } - - public static class Builder { - private final ImmutableMap.Builder params = - ImmutableMap.builder(); - - public Builder addParam(String param, String value) { - params.put(param, value); - return this; - } - - public Params build() { - return new Params(params.build()); - } - } -} \ No newline at end of file diff --git a/src/main/java/analyzer/exercises/hamming/AvoidCharacterLiterals.java b/src/main/java/analyzer/exercises/hamming/AvoidCharacterLiterals.java new file mode 100644 index 00000000..66166d11 --- /dev/null +++ b/src/main/java/analyzer/exercises/hamming/AvoidCharacterLiterals.java @@ -0,0 +1,13 @@ +package analyzer.exercises.hamming; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class AvoidCharacterLiterals extends Comment { + @Override + public String getKey() { + return "java.hamming.avoid_character_literals"; + } +} diff --git a/src/main/java/analyzer/exercises/hamming/CalculateDistanceInConstructor.java b/src/main/java/analyzer/exercises/hamming/CalculateDistanceInConstructor.java new file mode 100644 index 00000000..c83c3a9d --- /dev/null +++ b/src/main/java/analyzer/exercises/hamming/CalculateDistanceInConstructor.java @@ -0,0 +1,13 @@ +package analyzer.exercises.hamming; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class CalculateDistanceInConstructor extends Comment { + @Override + public String getKey() { + return "java.hamming.calculate_distance_in_constructor"; + } +} diff --git a/src/main/java/analyzer/exercises/hamming/Hamming.java b/src/main/java/analyzer/exercises/hamming/Hamming.java deleted file mode 100644 index 748f1940..00000000 --- a/src/main/java/analyzer/exercises/hamming/Hamming.java +++ /dev/null @@ -1,133 +0,0 @@ -package analyzer.exercises.hamming; - -import analyzer.exercises.Exercise; -import analyzer.exercises.GeneralComment; -import analyzer.exercises.Params; - -import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; - -import java.io.File; -import java.util.Set; - -public class Hamming extends Exercise { - - public Hamming(String inputDirectory, String outputDirectory) { - this(inputDirectory, outputDirectory, WriteAnalysisToFile.YES); - } - - public Hamming(String inputDirectory, - String outputDirectory, - WriteAnalysisToFile writeAnalysisToFile) { - super(inputDirectory, "Hamming.java", outputDirectory, writeAnalysisToFile); - } - - /** - * {@inheritDoc} - */ - @Deprecated - public Hamming(String inputDirectory) { - this(inputDirectory, WriteAnalysisToFile.YES); - } - - /** - * {@inheritDoc} - */ - @Deprecated - public Hamming(String inputDirectory, WriteAnalysisToFile writeAnalysisToFile) { - super(inputDirectory, "Hamming.java", writeAnalysisToFile); - } - - /** For testing. */ - public Hamming(File solutionFile) { - super(solutionFile); - } - - @Override - public void parse(CompilationUnit compilationUnit) { - HammingWalker walker = new HammingWalker(); - - compilationUnit.walk(ClassOrInterfaceDeclaration.class, walker); - - if (!walker.hasHammingClass()) { - addComment( - GeneralComment.USE_PROPER_CLASS_NAME, - Params.newBuilder().addParam(GeneralComment.CLASS_NAME, "Hamming").build()); - return; - } - - if (!walker.hasGetHammingDistanceMethod()) { - addComment( - GeneralComment.USE_PROPER_METHOD_NAME, - Params.newBuilder().addParam(GeneralComment.METHOD_NAME, "getHammingDistance").build()); - return; - } - - if (!walker.hasConstructor()) { - addComment(HammingComment.MUST_USE_CONSTRUCTOR); - return; - } - - if (!walker.constructorHasIfStatements() && !walker.constructorHasMethodCalls()) { - addComment(HammingComment.MUST_USE_CONDITIONAL_LOGIC_IN_CONSTRUCTOR); - return; - } - - if (!walker.constructorThrowsIllegalArgument()) { - addComment(HammingComment.MUST_THROW_IN_CONSTRUCTOR); - return; - } - - if (!walker.getHammingDistanceMethodMayCalculateDistance() - && !walker.constructorMayCalculateDistance()) { - addComment(HammingComment.MUST_CALCULATE_HAMMING_DISTANCE); - return; - } - - if (walker.usesCharacterLiterals()) { - addComment(HammingComment.AVOID_CHARACTER_LITERALS); - return; - } - - if (!walker.usesStringCharAtOrCodePointAt()) { - addComment(HammingComment.MUST_USE_STRING_CHAR_AT_OR_CODE_POINT_AT); - return; - } - - if (!walker.constructorMayCalculateDistance()) { - addComment(HammingComment.CALCULATE_DISTANCE_IN_CONSTRUCTOR); - } - - if (!walker.usesStringIsEmpty()) { - addComment(HammingComment.SHOULD_USE_STRING_IS_EMPTY); - } - - if (walker.shouldUseStreamFilterAndCount()) { - addComment(HammingComment.SHOULD_USE_STREAM_FILTER_AND_COUNT); - } - - Set longConstructors = walker.getLongConstructors(); - if (!longConstructors.isEmpty()) { - addComment( - GeneralComment.CONSTRUCTOR_TOO_LONG, - Params.newBuilder() - .addParam( - GeneralComment.CONSTRUCTOR_NAMES, formatNames(longConstructors)) - .build()); - } - - Set longMethods = walker.getLongMethods(); - if (!longMethods.isEmpty()) { - addComment( - GeneralComment.METHOD_TOO_LONG, - Params.newBuilder() - .addParam( - GeneralComment.METHOD_NAMES, formatNames(longMethods)) - .build()); - } - } - - private static String formatNames(Set names) { - return String.join(", ", names); - } -} diff --git a/src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java b/src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java new file mode 100644 index 00000000..2dc1ca79 --- /dev/null +++ b/src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java @@ -0,0 +1,86 @@ +package analyzer.exercises.hamming; + +import analyzer.Analysis; +import analyzer.Analyzer; +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; + +public class HammingAnalyzer implements Analyzer { + + @Override + public void analyze(List compilationUnits, 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; + } + + if (!walker.hasConstructor()) { + analysis.addComment(new MustUseConstructor()); + return; + } + + if (!walker.constructorHasIfStatements() && !walker.constructorHasMethodCalls()) { + analysis.addComment(new MustUseConditionalLogicInConstructor()); + return; + } + + if (!walker.constructorThrowsIllegalArgument()) { + analysis.addComment(new MustThrowInConstructor()); + return; + } + + if (!walker.getHammingDistanceMethodMayCalculateDistance() + && !walker.constructorMayCalculateDistance()) { + analysis.addComment(new MustCalculateHammingDistance()); + return; + } + + if (walker.usesCharacterLiterals()) { + analysis.addComment(new AvoidCharacterLiterals()); + return; + } + + if (!walker.usesStringCharAtOrCodePointAt()) { + analysis.addComment(new MustUseStringCharAtOrCodePointAt()); + return; + } + + if (!walker.constructorMayCalculateDistance()) { + analysis.addComment(new CalculateDistanceInConstructor()); + } + + if (!walker.usesStringIsEmpty()) { + analysis.addComment(new ShouldUseStringIsEmpty()); + } + + if (walker.shouldUseStreamFilterAndCount()) { + analysis.addComment(new ShouldUseStreamFilterAndCount()); + } + + Set longConstructors = walker.getLongConstructors(); + if (!longConstructors.isEmpty()) { + analysis.addComment(new ConstructorTooLong(longConstructors)); + } + + Set longMethods = walker.getLongMethods(); + if (!longMethods.isEmpty()) { + analysis.addComment(new MethodTooLong(longMethods)); + } + } +} diff --git a/src/main/java/analyzer/exercises/hamming/HammingComment.java b/src/main/java/analyzer/exercises/hamming/HammingComment.java deleted file mode 100644 index f135a1fa..00000000 --- a/src/main/java/analyzer/exercises/hamming/HammingComment.java +++ /dev/null @@ -1,20 +0,0 @@ -package analyzer.exercises.hamming; - -import analyzer.exercises.Comment; - -public enum HammingComment implements Comment { - MUST_USE_CONSTRUCTOR, - MUST_USE_CONDITIONAL_LOGIC_IN_CONSTRUCTOR, - MUST_THROW_IN_CONSTRUCTOR, - MUST_CALCULATE_HAMMING_DISTANCE, - CALCULATE_DISTANCE_IN_CONSTRUCTOR, - AVOID_CHARACTER_LITERALS, - SHOULD_USE_STRING_IS_EMPTY, - MUST_USE_STRING_CHAR_AT_OR_CODE_POINT_AT, - SHOULD_USE_STREAM_FILTER_AND_COUNT; - - @Override - public String toJson() { - return "java.hamming." + name().toLowerCase(); - } -} \ No newline at end of file diff --git a/src/main/java/analyzer/exercises/hamming/MustCalculateHammingDistance.java b/src/main/java/analyzer/exercises/hamming/MustCalculateHammingDistance.java new file mode 100644 index 00000000..f572a144 --- /dev/null +++ b/src/main/java/analyzer/exercises/hamming/MustCalculateHammingDistance.java @@ -0,0 +1,13 @@ +package analyzer.exercises.hamming; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class MustCalculateHammingDistance extends Comment { + @Override + public String getKey() { + return "java.hamming.must_calculate_hamming_distance"; + } +} diff --git a/src/main/java/analyzer/exercises/hamming/MustThrowInConstructor.java b/src/main/java/analyzer/exercises/hamming/MustThrowInConstructor.java new file mode 100644 index 00000000..f49db8e5 --- /dev/null +++ b/src/main/java/analyzer/exercises/hamming/MustThrowInConstructor.java @@ -0,0 +1,13 @@ +package analyzer.exercises.hamming; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class MustThrowInConstructor extends Comment { + @Override + public String getKey() { + return "java.hamming.must_throw_in_constructor"; + } +} diff --git a/src/main/java/analyzer/exercises/hamming/MustUseConditionalLogicInConstructor.java b/src/main/java/analyzer/exercises/hamming/MustUseConditionalLogicInConstructor.java new file mode 100644 index 00000000..d4e7b2c9 --- /dev/null +++ b/src/main/java/analyzer/exercises/hamming/MustUseConditionalLogicInConstructor.java @@ -0,0 +1,13 @@ +package analyzer.exercises.hamming; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class MustUseConditionalLogicInConstructor extends Comment { + @Override + public String getKey() { + return "java.hamming.must_use_conditional_logic_in_constructor"; + } +} diff --git a/src/main/java/analyzer/exercises/hamming/MustUseConstructor.java b/src/main/java/analyzer/exercises/hamming/MustUseConstructor.java new file mode 100644 index 00000000..dba1234d --- /dev/null +++ b/src/main/java/analyzer/exercises/hamming/MustUseConstructor.java @@ -0,0 +1,13 @@ +package analyzer.exercises.hamming; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class MustUseConstructor extends Comment { + @Override + public String getKey() { + return "java.hamming.must_use_constructor"; + } +} diff --git a/src/main/java/analyzer/exercises/hamming/MustUseStringCharAtOrCodePointAt.java b/src/main/java/analyzer/exercises/hamming/MustUseStringCharAtOrCodePointAt.java new file mode 100644 index 00000000..54e68929 --- /dev/null +++ b/src/main/java/analyzer/exercises/hamming/MustUseStringCharAtOrCodePointAt.java @@ -0,0 +1,13 @@ +package analyzer.exercises.hamming; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class MustUseStringCharAtOrCodePointAt extends Comment { + @Override + public String getKey() { + return "java.hamming.must_use_string_char_at_or_code_point_at"; + } +} diff --git a/src/main/java/analyzer/exercises/hamming/ShouldUseStreamFilterAndCount.java b/src/main/java/analyzer/exercises/hamming/ShouldUseStreamFilterAndCount.java new file mode 100644 index 00000000..1b61ad7e --- /dev/null +++ b/src/main/java/analyzer/exercises/hamming/ShouldUseStreamFilterAndCount.java @@ -0,0 +1,13 @@ +package analyzer.exercises.hamming; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class ShouldUseStreamFilterAndCount extends Comment { + @Override + public String getKey() { + return "java.hamming.should_use_stream_filter_and_count"; + } +} diff --git a/src/main/java/analyzer/exercises/hamming/ShouldUseStringIsEmpty.java b/src/main/java/analyzer/exercises/hamming/ShouldUseStringIsEmpty.java new file mode 100644 index 00000000..2595ea02 --- /dev/null +++ b/src/main/java/analyzer/exercises/hamming/ShouldUseStringIsEmpty.java @@ -0,0 +1,13 @@ +package analyzer.exercises.hamming; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class ShouldUseStringIsEmpty extends Comment { + @Override + public String getKey() { + return "java.hamming.should_use_string_is_empty"; + } +} diff --git a/src/main/java/analyzer/exercises/twofer/AvoidStringFormat.java b/src/main/java/analyzer/exercises/twofer/AvoidStringFormat.java new file mode 100644 index 00000000..eebd1579 --- /dev/null +++ b/src/main/java/analyzer/exercises/twofer/AvoidStringFormat.java @@ -0,0 +1,13 @@ +package analyzer.exercises.twofer; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class AvoidStringFormat extends Comment { + @Override + public String getKey() { + return "java.two-fer.avoid_string_format"; + } +} diff --git a/src/main/java/analyzer/exercises/twofer/Twofer.java b/src/main/java/analyzer/exercises/twofer/Twofer.java deleted file mode 100644 index de583779..00000000 --- a/src/main/java/analyzer/exercises/twofer/Twofer.java +++ /dev/null @@ -1,76 +0,0 @@ -package analyzer.exercises.twofer; - -import java.io.File; - -import com.github.javaparser.ast.CompilationUnit; - -import analyzer.exercises.Exercise; -import analyzer.exercises.GeneralComment; -import analyzer.exercises.Params; - -public class Twofer extends Exercise { - - public Twofer(String inputDirectory, String outputDirectory) { - this(inputDirectory, outputDirectory, WriteAnalysisToFile.YES); - } - - public Twofer(String inputDirectory, - String outputDirectory, - WriteAnalysisToFile writeAnalysisToFile) { - super(inputDirectory, "Twofer.java", outputDirectory, writeAnalysisToFile); - } - - /** - * @deprecated {@inheritDoc} - */ - @Deprecated - public Twofer(String inputDirectory) { - this(inputDirectory, WriteAnalysisToFile.YES); - } - - /** - * @deprecated {@inheritDoc} - */ - @Deprecated - public Twofer(String inputDirectory, WriteAnalysisToFile writeAnalysisToFile) { - super(inputDirectory, "Twofer.java", writeAnalysisToFile); - } - - /** For testing. */ - public Twofer(File solutionFile) { - super(solutionFile); - } - - @Override - public void parse(CompilationUnit compilationUnit) { - TwoferWalker walker = new TwoferWalker(); - - compilationUnit.walk(walker); - - if (!walker.hasClassTwofer) { - addComment( - GeneralComment.USE_PROPER_CLASS_NAME, - Params.newBuilder().addParam(GeneralComment.CLASS_NAME, "Twofer").build()); - } else if (!walker.hasMethodTwofer) { - addComment( - GeneralComment.USE_PROPER_METHOD_NAME, - Params.newBuilder().addParam(GeneralComment.METHOD_NAME, "twofer").build()); - } else if (walker.hasHardCodedTestCases) { - addComment(GeneralComment.AVOID_HARD_CODED_TEST_CASES); - } else if (walker.usesLambda) { - // could be used later for additional comments? - } else if (walker.usesLoops) { - // could be used later for additional comments? - } else if (!walker.hasMethodCall && !(walker.usesIfStatement || walker.usesConditional)) { - addComment(TwoferComment.USE_CONDITIONAL_LOGIC); - } else if (walker.usesFormat) { - addComment(TwoferComment.AVOID_STRING_FORMAT); - } else if (walker.returnCount > 1) { - addComment(TwoferComment.USE_ONE_RETURN); - } else { - if (walker.usesIfStatement) { - addComment(TwoferComment.USE_TERNARY_OPERATOR); - } - } - } -} diff --git a/src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java b/src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java new file mode 100644 index 00000000..a2b5a93f --- /dev/null +++ b/src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java @@ -0,0 +1,42 @@ +package analyzer.exercises.twofer; + +import analyzer.Analysis; +import analyzer.Analyzer; +import analyzer.comments.AvoidHardCodedTestCases; +import analyzer.comments.UseProperClassName; +import analyzer.comments.UseProperMethodName; +import com.github.javaparser.ast.CompilationUnit; + +import java.util.List; + +public class TwoferAnalyzer implements Analyzer { + + @Override + public void analyze(List compilationUnits, Analysis analysis) { + TwoferWalker walker = new TwoferWalker(); + + compilationUnits.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) { + analysis.addComment(new AvoidHardCodedTestCases()); + } else if (walker.usesLambda) { + // could be used later for additional comments? + } else if (walker.usesLoops) { + // could be used later for additional comments? + } else if (!walker.hasMethodCall && !(walker.usesIfStatement || walker.usesConditional)) { + analysis.addComment(new UseConditionalLogic()); + } else if (walker.usesFormat) { + analysis.addComment(new AvoidStringFormat()); + } else if (walker.returnCount > 1) { + analysis.addComment(new UseOneReturn()); + } else { + if (walker.usesIfStatement) { + analysis.addComment(new UseTernaryOperator()); + } + } + } +} diff --git a/src/main/java/analyzer/exercises/twofer/TwoferComment.java b/src/main/java/analyzer/exercises/twofer/TwoferComment.java deleted file mode 100644 index 0814a4b7..00000000 --- a/src/main/java/analyzer/exercises/twofer/TwoferComment.java +++ /dev/null @@ -1,15 +0,0 @@ -package analyzer.exercises.twofer; - -import analyzer.exercises.Comment; - -public enum TwoferComment implements Comment { - AVOID_STRING_FORMAT, - USE_CONDITIONAL_LOGIC, - USE_ONE_RETURN, - USE_TERNARY_OPERATOR; - - @Override - public String toJson() { - return "java.two-fer." + name().toLowerCase(); - } -} \ No newline at end of file diff --git a/src/main/java/analyzer/exercises/twofer/UseConditionalLogic.java b/src/main/java/analyzer/exercises/twofer/UseConditionalLogic.java new file mode 100644 index 00000000..675fda41 --- /dev/null +++ b/src/main/java/analyzer/exercises/twofer/UseConditionalLogic.java @@ -0,0 +1,13 @@ +package analyzer.exercises.twofer; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class UseConditionalLogic extends Comment { + @Override + public String getKey() { + return "java.two-fer.use_conditional_logic"; + } +} diff --git a/src/main/java/analyzer/exercises/twofer/UseOneReturn.java b/src/main/java/analyzer/exercises/twofer/UseOneReturn.java new file mode 100644 index 00000000..21c6383e --- /dev/null +++ b/src/main/java/analyzer/exercises/twofer/UseOneReturn.java @@ -0,0 +1,13 @@ +package analyzer.exercises.twofer; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class UseOneReturn extends Comment { + @Override + public String getKey() { + return "java.two-fer.use_one_return"; + } +} diff --git a/src/main/java/analyzer/exercises/twofer/UseTernaryOperator.java b/src/main/java/analyzer/exercises/twofer/UseTernaryOperator.java new file mode 100644 index 00000000..156d8d86 --- /dev/null +++ b/src/main/java/analyzer/exercises/twofer/UseTernaryOperator.java @@ -0,0 +1,13 @@ +package analyzer.exercises.twofer; + +import analyzer.Comment; + +/** + * @see Markdown Template + */ +class UseTernaryOperator extends Comment { + @Override + public String getKey() { + return "java.two-fer.use_ternary_operator"; + } +} diff --git a/src/test/java/analyzer/ExerciseAnalyzerTest.java b/src/test/java/analyzer/ExerciseAnalyzerTest.java new file mode 100644 index 00000000..72fcb6e3 --- /dev/null +++ b/src/test/java/analyzer/ExerciseAnalyzerTest.java @@ -0,0 +1,17 @@ +package analyzer; + +import com.github.javaparser.StaticJavaParser; + +import java.util.List; + +public abstract class ExerciseAnalyzerTest { + protected abstract Analyzer getAnalyzer(); + + protected Analysis analyzeResourceFile(String resourceFileName) { + var resource = getClass().getResourceAsStream(resourceFileName); + var compilationUnit = StaticJavaParser.parse(resource); + var analysis = new Analysis(); + getAnalyzer().analyze(List.of(compilationUnit), analysis); + return analysis; + } +} diff --git a/src/test/java/analyzer/OutputWriterTest.java b/src/test/java/analyzer/OutputWriterTest.java new file mode 100644 index 00000000..edeca967 --- /dev/null +++ b/src/test/java/analyzer/OutputWriterTest.java @@ -0,0 +1,133 @@ +package analyzer; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Map; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OutputWriterTest { + + private StringWriter analysisOutput; + private StringWriter tagsOutput; + private OutputWriter outputWriter; + + @BeforeEach + public void setup() { + analysisOutput = new StringWriter(); + tagsOutput = new StringWriter(); + outputWriter = new OutputWriter(analysisOutput, tagsOutput); + } + + @Test + public void serializeAnalysis() throws IOException { + var analysis = new Analysis(); + 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("key_and_type", CommentType.ACTIONABLE)); + analysis.addComment(new TestComment("key_type_and_params", CommentType.ACTIONABLE, Map.of("param", "value"))); + analysis.setSummary("Lorum Ipsum"); + outputWriter.write(analysis); + + var expected = """ + { + "summary": "Lorum Ipsum", + "comments": [ + {"comment": "key_only"}, + { + "comment": "key_and_single_param", + "params": {"param1": "value1"} + }, + { + "comment": "key_and_multiple_params", + "params": { + "param1": "value1", + "param2": "value2" + } + }, + { + "comment": "key_and_type", + "type": "actionable" + }, + { + "comment": "key_type_and_params", + "type": "actionable", + "params": {"param": "value"} + } + ] + } + """.trim(); + + assertThat(analysisOutput.toString()).isEqualTo(expected); + } + + @Test + public void serializeTags() throws IOException { + var analysis = new Analysis(); + analysis.addTag("tag1"); + analysis.addTag("tag3"); + analysis.addTag("tag2"); + outputWriter.write(analysis); + + var expected = """ + {"tags": [ + "tag1", + "tag3", + "tag2" + ]} + """.trim(); + + assertThat(tagsOutput.toString()).isEqualTo(expected); + } + + @Test + public void serializeEmptyAnalysis() throws IOException { + outputWriter.write(new Analysis()); + assertThat(analysisOutput.toString()).isEqualTo("{}"); + assertThat(tagsOutput.toString()).isEqualTo("{}"); + } + + private static class TestComment extends Comment { + private final String key; + private final CommentType type; + private final Map parameters; + + private TestComment(String key, CommentType type, Map parameters) { + this.key = Objects.requireNonNull(key); + this.type = type; + this.parameters = Objects.requireNonNull(parameters); + } + + private TestComment(String key) { + this(key, null, Map.of()); + } + + private TestComment(String key, CommentType type) { + this(key, type, Map.of()); + } + + private TestComment(String key, Map parameters) { + this(key, null, parameters); + } + + @Override + public String getKey() { + return this.key; + } + + @Override + public CommentType getType() { + return this.type; + } + + @Override + public Map getParameters() { + return this.parameters; + } + } +} diff --git a/src/test/java/analyzer/exercises/hamming/HammingAnalyzerTest.java b/src/test/java/analyzer/exercises/hamming/HammingAnalyzerTest.java new file mode 100644 index 00000000..eb71d057 --- /dev/null +++ b/src/test/java/analyzer/exercises/hamming/HammingAnalyzerTest.java @@ -0,0 +1,57 @@ +package analyzer.exercises.hamming; + +import analyzer.Analyzer; +import analyzer.Comment; +import analyzer.ExerciseAnalyzerTest; +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; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class HammingAnalyzerTest extends ExerciseAnalyzerTest { + @Override + protected Analyzer getAnalyzer() { + return new HammingAnalyzer(); + } + + 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()}), + Arguments.of("NoCalculationOfHammingDistance.java.txt", new Comment[]{new MustCalculateHammingDistance()}), + Arguments.of("UsesCharacterLiterals.java.txt", new Comment[]{new AvoidCharacterLiterals()}), + Arguments.of("MustUseCharAtOrCodePointAt.java.txt", new Comment[]{new MustUseStringCharAtOrCodePointAt()}), + Arguments.of("NestedValidation.java.txt", new Comment[]{new CalculateDistanceInConstructor()}), + Arguments.of("NestedCalculation.java.txt", new Comment[0]), + Arguments.of("ShouldUseStringIsEmpty.java.txt", new Comment[]{new ShouldUseStringIsEmpty()}), + Arguments.of("OptimalWithCalculationInGetHammingDistance.java.txt", new Comment[]{new CalculateDistanceInConstructor()}), + Arguments.of("OptimalWithCalculationDelegatedFromGetHammingDistance.java.txt", new Comment[]{new CalculateDistanceInConstructor()}), + Arguments.of("ConstructorTooLong.java.txt", new Comment[]{new ConstructorTooLong("Hamming")}), + Arguments.of("MethodTooLong.java.txt", new Comment[]{new MethodTooLong("calculateHammingDistance")}), + Arguments.of("UsesStreamReduce.java.txt", new Comment[]{new ShouldUseStreamFilterAndCount()}), + Arguments.of("OptimalWithCalculationDelegatedFromConstructor.java.txt", new Comment[0]), + Arguments.of("OptimalWithValidationMethod.java.txt", new Comment[0])); + } + + @MethodSource("testCases") + @ParameterizedTest(name = "{0}") + public void testCommentsOnSolution(String solutionFile, Comment... expectedComments) { + var actual = analyzeResourceFile(getResourceFileName(solutionFile)); + + assertThat(actual.getComments()).containsExactly(expectedComments); + } + + private static String getResourceFileName(String testFileName) { + return "/analyzer/exercises/hamming/" + testFileName; + } +} diff --git a/src/test/java/analyzer/exercises/hamming/HammingTest.java b/src/test/java/analyzer/exercises/hamming/HammingTest.java deleted file mode 100644 index 910d3740..00000000 --- a/src/test/java/analyzer/exercises/hamming/HammingTest.java +++ /dev/null @@ -1,312 +0,0 @@ -package analyzer.exercises.hamming; - - -import analyzer.exercises.Exercise; -import org.json.JSONArray; -import org.json.JSONObject; -import org.junit.jupiter.api.Test; - -import java.io.File; - -import static org.assertj.core.api.Assertions.assertThat; - -public class HammingTest { - private static final int INDENTATION_LEVEL = 1; - - @Test - public void noHammingClass() { - Exercise hamming = - new Hamming(getTestFileFromResource("NoHammingClass.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put("comments", - new JSONArray() - .put( - new JSONObject() - .put("comment", "java.general.use_proper_class_name") - .put("params", new JSONObject().put("className", "Hamming")))) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void noGetHammingDistanceMethod() { - Exercise hamming = - new Hamming(getTestFileFromResource("NoGetHammingDistanceMethod.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put("comments", - new JSONArray() - .put( - new JSONObject() - .put("comment", "java.general.use_proper_method_name") - .put( - "params", - new JSONObject().put("methodName", "getHammingDistance")))) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void noConstructor() { - Exercise hamming = - new Hamming(getTestFileFromResource("NoConstructor.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put("comments", new JSONArray().put("java.hamming.must_use_constructor")) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void noConditionalInConstructor() { - Exercise hamming = - new Hamming(getTestFileFromResource("NoConditionalInConstructor.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put( - "comments", - new JSONArray() - .put("java.hamming.must_use_conditional_logic_in_constructor")) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void doesNotThrowInConstructor() { - Exercise hamming = - new Hamming(getTestFileFromResource("DoesNotThrowInConstructor.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put( - "comments", - new JSONArray().put("java.hamming.must_throw_in_constructor")) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void noCalculationOfHammingDistance() { - Exercise hamming = - new Hamming(getTestFileFromResource("NoCalculationOfHammingDistance.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put( - "comments", - new JSONArray().put("java.hamming.must_calculate_hamming_distance")) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void usesCharacterLiterals() { - Exercise hamming = - new Hamming(getTestFileFromResource("UsesCharacterLiterals.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put( - "comments", - new JSONArray().put("java.hamming.avoid_character_literals")) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void mustUseCharAtOrCodePointAt() { - Exercise hamming = - new Hamming( - getTestFileFromResource( - "MustUseCharAtOrCodePointAt.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put( - "comments", - new JSONArray().put( - "java.hamming.must_use_string_char_at_or_code_point_at")) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void nestedValidation() { - Exercise hamming = - new Hamming(getTestFileFromResource("NestedValidation.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put( - "comments", - new JSONArray().put("java.hamming.calculate_distance_in_constructor")) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void nestedCalculation() { - Exercise hamming = - new Hamming(getTestFileFromResource("NestedCalculation.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo(new JSONObject().toString(INDENTATION_LEVEL)); - } - - @Test - public void shouldUseIsEmpty() { - Exercise hamming = - new Hamming( - getTestFileFromResource( - "ShouldUseStringIsEmpty.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put( - "comments", - new JSONArray().put("java.hamming.should_use_string_is_empty")) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void optimalWithCalculationInGetHammingDistance() { - Exercise hamming = - new Hamming( - getTestFileFromResource( - "OptimalWithCalculationInGetHammingDistance.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put( - "comments", - new JSONArray().put("java.hamming.calculate_distance_in_constructor")) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void optimalWithCalculationDelegatedFromGetHammingDistance() { - Exercise hamming = - new Hamming( - getTestFileFromResource( - "OptimalWithCalculationDelegatedFromGetHammingDistance.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put( - "comments", - new JSONArray().put("java.hamming.calculate_distance_in_constructor")) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void constructorTooLong() { - Exercise hamming = - new Hamming( - getTestFileFromResource("ConstructorTooLong.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put("comments", - new JSONArray() - .put( - new JSONObject() - .put("comment", "java.general.constructor_too_long") - .put( - "params", - new JSONObject().put( - "constructorNames", "Hamming")))) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void methodTooLong() { - Exercise hamming = - new Hamming( - getTestFileFromResource("MethodTooLong.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put("comments", - new JSONArray() - .put( - new JSONObject() - .put("comment", "java.general.method_too_long") - .put( - "params", - new JSONObject().put( - "methodNames", "calculateHammingDistance")))) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void usesStreamReduce() { - Exercise hamming = - new Hamming( - getTestFileFromResource("UsesStreamReduce.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject() - .put( - "comments", - new JSONArray().put("java.hamming.should_use_stream_filter_and_count")) - .toString(INDENTATION_LEVEL)); - } - - @Test - public void optimalWithCalculationDelegatedFromConstructor() { - Exercise hamming = - new Hamming( - getTestFileFromResource( - "OptimalWithCalculationDelegatedFromConstructor.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject().toString(INDENTATION_LEVEL)); - } - - @Test - public void optimalWithValidationMethod() { - Exercise hamming = - new Hamming(getTestFileFromResource("OptimalWithValidationMethod.java.txt")); - hamming.parse(); - - assertThat(hamming.getAnalysis().toString(INDENTATION_LEVEL)) - .isEqualTo( - new JSONObject().toString(INDENTATION_LEVEL)); - } - - private File getTestFileFromResource(String testFileName) { - return new File( - getClass() - .getResource("/analyzer/exercises/hamming/" + testFileName) - .getFile()); - } -} diff --git a/src/test/java/analyzer/exercises/twofer/TwoferAnalyzerTest.java b/src/test/java/analyzer/exercises/twofer/TwoferAnalyzerTest.java new file mode 100644 index 00000000..58e5f026 --- /dev/null +++ b/src/test/java/analyzer/exercises/twofer/TwoferAnalyzerTest.java @@ -0,0 +1,50 @@ +package analyzer.exercises.twofer; + +import analyzer.Analyzer; +import analyzer.Comment; +import analyzer.ExerciseAnalyzerTest; +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; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TwoferAnalyzerTest extends ExerciseAnalyzerTest { + + @Override + protected Analyzer getAnalyzer() { + return new TwoferAnalyzer(); + } + + 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()}), + Arguments.of("NoConditionalLogic.java.txt", new Comment[]{new UseConditionalLogic()}), + Arguments.of("UsesStringFormat.java.txt", new Comment[]{new AvoidStringFormat()}), + Arguments.of("UsesMultipleReturns.java.txt", new Comment[]{new UseOneReturn()}), + Arguments.of("OptimalNoTernary.java.txt", new Comment[]{new UseTernaryOperator()}), + Arguments.of("Optimal.java.txt", new Comment[0]) + ); + } + + @MethodSource("testCases") + @ParameterizedTest(name = "{0}") + public void testCommentsOnSolution(String solutionFile, Comment... expectedComments) { + var actual = analyzeResourceFile(getResourceFileName(solutionFile)); + + assertThat(actual.getComments()).containsExactly(expectedComments); + } + + private static String getResourceFileName(String testFileName) { + return "/analyzer/exercises/twofer/" + testFileName; + } +} diff --git a/src/test/java/analyzer/exercises/twofer/TwoferTest.java b/src/test/java/analyzer/exercises/twofer/TwoferTest.java deleted file mode 100644 index 751cd9fa..00000000 --- a/src/test/java/analyzer/exercises/twofer/TwoferTest.java +++ /dev/null @@ -1,155 +0,0 @@ -package analyzer.exercises.twofer; - -import analyzer.exercises.Exercise; -import org.json.JSONArray; -import org.json.JSONObject; -import org.junit.jupiter.api.Test; - -import java.io.File; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TwoferTest { - @Test - public void noTwoferClass() { - Exercise twofer = - new Twofer(getTestFileFromResource("NoTwoferClass.java.txt")); - twofer.parse(); - - assertThat(twofer.getAnalysis().toString()) - .isEqualTo( - new JSONObject() - .put("comments", - new JSONArray() - .put( - new JSONObject() - .put("comment", "java.general.use_proper_class_name") - .put("params", new JSONObject().put("className", "Twofer")))) - .toString()); - } - - @Test - public void noTwoferMethod() { - Exercise twofer = - new Twofer(getTestFileFromResource("NoTwoferMethod.java.txt")); - twofer.parse(); - - assertThat(twofer.getAnalysis().toString()) - .isEqualTo( - new JSONObject() - .put("comments", - new JSONArray() - .put( - new JSONObject() - .put("comment", "java.general.use_proper_method_name") - .put("params", new JSONObject().put("methodName", "twofer")))) - .toString()); - } - - @Test - public void usesLambda() { - Exercise twofer = - new Twofer(getTestFileFromResource("UsesLambda.java.txt")); - twofer.parse(); - - assertThat(twofer.getAnalysis().toString()) - .isEqualTo(new JSONObject().toString()); - } - - @Test - public void usesLoop() { - Exercise twofer = - new Twofer(getTestFileFromResource("UsesLoop.java.txt")); - twofer.parse(); - - assertThat(twofer.getAnalysis().toString()) - .isEqualTo(new JSONObject().toString()); - } - - @Test - public void hardCodedTestCases() { - Exercise twofer = - new Twofer(getTestFileFromResource("HardCodedTestCases.java.txt")); - twofer.parse(); - - assertThat(twofer.getAnalysis().toString()) - .isEqualTo( - new JSONObject() - .put("comments", - new JSONArray().put("java.general.avoid_hard_coded_test_cases")) - .toString()); - } - - @Test - public void noConditionalLogic() { - Exercise twofer = - new Twofer(getTestFileFromResource("NoConditionalLogic.java.txt")); - twofer.parse(); - - assertThat(twofer.getAnalysis().toString()) - .isEqualTo( - new JSONObject() - .put("comments", - new JSONArray().put("java.two-fer.use_conditional_logic")) - .toString()); - } - - @Test - public void usesStringFormat() { - Exercise twofer = - new Twofer(getTestFileFromResource("UsesStringFormat.java.txt")); - twofer.parse(); - - assertThat(twofer.getAnalysis().toString()) - .isEqualTo( - new JSONObject() - .put("comments", - new JSONArray().put("java.two-fer.avoid_string_format")) - .toString()); - } - - @Test - public void usesMultipleReturns() { - Exercise twofer = - new Twofer(getTestFileFromResource("UsesMultipleReturns.java.txt")); - twofer.parse(); - - assertThat(twofer.getAnalysis().toString()) - .isEqualTo( - new JSONObject() - .put("comments", - new JSONArray().put("java.two-fer.use_one_return")) - .toString()); - } - - @Test - public void optimalNoTernary() { - Exercise twofer = - new Twofer(getTestFileFromResource("OptimalNoTernary.java.txt")); - twofer.parse(); - - assertThat(twofer.getAnalysis().toString()) - .isEqualTo( - new JSONObject() - .put("comments", - new JSONArray().put("java.two-fer.use_ternary_operator")) - .toString()); - } - - @Test - public void optimal() { - Exercise twofer = - new Twofer(getTestFileFromResource("Optimal.java.txt")); - twofer.parse(); - - assertThat(twofer.getAnalysis().toString()) - .isEqualTo(new JSONObject().toString()); - } - - private File getTestFileFromResource(String testFileName) { - return new File( - getClass() - .getResource("/analyzer/exercises/twofer/" + testFileName) - .getFile()); - } -} diff --git a/tests/hamming/expected_analysis.json b/tests/hamming/expected_analysis.json index f4698719..31d20f66 100644 --- a/tests/hamming/expected_analysis.json +++ b/tests/hamming/expected_analysis.json @@ -1 +1,4 @@ -{"comments":["java.hamming.calculate_distance_in_constructor","java.hamming.should_use_string_is_empty"]} \ No newline at end of file +{"comments": [ + {"comment": "java.hamming.calculate_distance_in_constructor"}, + {"comment": "java.hamming.should_use_string_is_empty"} +]} \ No newline at end of file diff --git a/tests/unknown-exercise/.meta/config.json b/tests/unknown-exercise/.meta/config.json new file mode 100644 index 00000000..acebb5d0 --- /dev/null +++ b/tests/unknown-exercise/.meta/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "sanderploegsma" + ], + "files": { + "solution": [ + "src/main/java/UnknownExercise.java" + ], + "test": [ + "src/test/java/UnknownExerciseTest.java" + ], + "example": [ + ".meta/src/reference/java/UnknownExercise.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Nothing to see here", + "source": "No source", + "source_url": "https://example.com" +} diff --git a/tests/unknown-exercise/expected_analysis.json b/tests/unknown-exercise/expected_analysis.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tests/unknown-exercise/expected_analysis.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/unknown-exercise/expected_tags.json b/tests/unknown-exercise/expected_tags.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tests/unknown-exercise/expected_tags.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/unknown-exercise/src/main/java/UnknownExercise.java b/tests/unknown-exercise/src/main/java/UnknownExercise.java new file mode 100644 index 00000000..9e6a0ca4 --- /dev/null +++ b/tests/unknown-exercise/src/main/java/UnknownExercise.java @@ -0,0 +1,5 @@ +class UnknownExercise { + int calculate() { + return 42; + } +}