diff --git a/bin/run-tests.sh b/bin/run-tests.sh index 8060ca08..b4d319bd 100755 --- a/bin/run-tests.sh +++ b/bin/run-tests.sh @@ -14,15 +14,15 @@ exit_code=0 # Iterate over all test directories -for test_dir in tests/*; do - test_dir_name=$(basename "${test_dir}") +for test_dir in $(find tests -name expected_analysis.json | rev | cut -d '/' -f 2- | rev); do test_dir_path=$(realpath "${test_dir}") + test_slug=$(echo "${test_dir}" | awk -F/ '{ print $2 }') - bin/run.sh "${test_dir_name}" "${test_dir_path}/" "${test_dir_path}/" + bin/run.sh "${test_slug}" "${test_dir_path}/" "${test_dir_path}/" for file in analysis.json tags.json; do expected_file="expected_${file}" - echo "${test_dir_name}: comparing ${file} to ${expected_file}" + echo "${test_dir}: comparing ${file} to ${expected_file}" if ! diff "${test_dir_path}/${file}" "${test_dir_path}/${expected_file}"; then exit_code=1 @@ -30,4 +30,4 @@ for test_dir in tests/*; do done done -exit ${exit_code} +exit ${exit_code} \ No newline at end of file diff --git a/src/main/java/analyzer/AnalyzerRoot.java b/src/main/java/analyzer/AnalyzerRoot.java index 57a37914..4d063f0f 100644 --- a/src/main/java/analyzer/AnalyzerRoot.java +++ b/src/main/java/analyzer/AnalyzerRoot.java @@ -3,6 +3,7 @@ import analyzer.comments.FeedbackRequest; import analyzer.exercises.GlobalAnalyzer; import analyzer.exercises.hamming.HammingAnalyzer; +import analyzer.exercises.leap.LeapAnalyzer; import analyzer.exercises.twofer.TwoferAnalyzer; import com.github.javaparser.ast.CompilationUnit; @@ -30,6 +31,7 @@ private static List createAnalyzers(String slug) { switch (slug) { case "hamming" -> analyzers.add(new HammingAnalyzer()); + case "leap" -> analyzers.add(new LeapAnalyzer()); case "two-fer" -> analyzers.add(new TwoferAnalyzer()); } diff --git a/src/main/java/analyzer/exercises/GlobalAnalyzer.java b/src/main/java/analyzer/exercises/GlobalAnalyzer.java index b35c2da1..0f31f99b 100644 --- a/src/main/java/analyzer/exercises/GlobalAnalyzer.java +++ b/src/main/java/analyzer/exercises/GlobalAnalyzer.java @@ -11,33 +11,31 @@ import java.util.List; -public class GlobalAnalyzer extends VoidVisitorAdapter implements Analyzer { - private Analysis analysis; +public class GlobalAnalyzer extends VoidVisitorAdapter implements Analyzer { @Override public void analyze(List compilationUnits, Analysis analysis) { - this.analysis = analysis; for (CompilationUnit compilationUnit : compilationUnits) { - compilationUnit.accept(this, null); + compilationUnit.accept(this, analysis); } } @Override - public void visit(MethodDeclaration n, Void arg) { - if (isMainMethod(n)) { + public void visit(MethodDeclaration node, Analysis analysis) { + if (isMainMethod(node)) { analysis.addComment(new DoNotUseMainMethod()); } - super.visit(n, arg); + super.visit(node, analysis); } @Override - public void visit(MethodCallExpr n, Void arg) { - if (isPrintStatement(n)) { + public void visit(MethodCallExpr node, Analysis analysis) { + if (isPrintStatement(node)) { analysis.addComment(new AvoidPrintStatements()); } - super.visit(n, arg); + super.visit(node, analysis); } private static boolean isMainMethod(MethodDeclaration node) { diff --git a/src/main/java/analyzer/exercises/leap/AvoidConditionalLogic.java b/src/main/java/analyzer/exercises/leap/AvoidConditionalLogic.java new file mode 100644 index 00000000..ce79baab --- /dev/null +++ b/src/main/java/analyzer/exercises/leap/AvoidConditionalLogic.java @@ -0,0 +1,19 @@ +package analyzer.exercises.leap; + +import analyzer.Comment; +import analyzer.CommentType; + +/** + * @see Markdown Template + */ +class AvoidConditionalLogic extends Comment { + @Override + public String getKey() { + return "java.leap.avoid_conditional_logic"; + } + + @Override + public CommentType getType() { + return CommentType.ACTIONABLE; + } +} diff --git a/src/main/java/analyzer/exercises/leap/LeapAnalyzer.java b/src/main/java/analyzer/exercises/leap/LeapAnalyzer.java new file mode 100644 index 00000000..fd5289cf --- /dev/null +++ b/src/main/java/analyzer/exercises/leap/LeapAnalyzer.java @@ -0,0 +1,96 @@ +package analyzer.exercises.leap; + +import analyzer.Analysis; +import analyzer.Analyzer; +import analyzer.comments.AvoidHardCodedTestCases; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.expr.BinaryExpr; +import com.github.javaparser.ast.expr.ConditionalExpr; +import com.github.javaparser.ast.expr.IntegerLiteralExpr; +import com.github.javaparser.ast.stmt.IfStmt; +import com.github.javaparser.ast.visitor.VoidVisitorAdapter; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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( + "java.time", + "java.util.GregorianCalendar" + ); + + private final Set intLiterals = new HashSet<>(); + + @Override + public void analyze(List compilationUnits, Analysis analysis) { + for (CompilationUnit compilationUnit : compilationUnits) { + compilationUnit.accept(this, analysis); + } + } + + @Override + public void visit(CompilationUnit node, Analysis analysis) { + // Reset state for each compilation unit + this.intLiterals.clear(); + + super.visit(node, analysis); + } + + @Override + public void visit(ImportDeclaration node, Analysis analysis) { + if (isUsingBuiltInMethods(node)) { + analysis.addComment(new NoBuiltInMethods()); + } + + super.visit(node, analysis); + } + + @Override + public void visit(IntegerLiteralExpr node, Analysis analysis) { + if (node.asNumber() instanceof Integer i) { + this.intLiterals.add(i); + } + + if (this.intLiterals.containsAll(TEST_CASES)) { + analysis.addComment(new AvoidHardCodedTestCases()); + } + + super.visit(node, analysis); + } + + @Override + public void visit(IfStmt node, Analysis analysis) { + analysis.addComment(new AvoidConditionalLogic()); + super.visit(node, analysis); + } + + @Override + public void visit(ConditionalExpr node, Analysis analysis) { + analysis.addComment(new AvoidConditionalLogic()); + super.visit(node, analysis); + } + + @Override + public void visit(MethodDeclaration node, Analysis analysis) { + if (node.getNameAsString().equals("isLeapYear") && hasMoreThanThreeChecks(node)) { + analysis.addComment(new UseMinimumNumberOfChecks()); + } + super.visit(node, analysis); + } + + private static boolean isUsingBuiltInMethods(ImportDeclaration node) { + var name = node.getNameAsString(); + return DISALLOWED_IMPORTS.stream().anyMatch(name::contains); + } + + private static boolean hasMoreThanThreeChecks(MethodDeclaration node) { + var booleanOperators = node.findAll(BinaryExpr.class, + x -> x.getOperator() == BinaryExpr.Operator.AND || x.getOperator() == BinaryExpr.Operator.OR); + + return booleanOperators.size() > 2; + } +} diff --git a/src/main/java/analyzer/exercises/leap/NoBuiltInMethods.java b/src/main/java/analyzer/exercises/leap/NoBuiltInMethods.java new file mode 100644 index 00000000..d7181cf7 --- /dev/null +++ b/src/main/java/analyzer/exercises/leap/NoBuiltInMethods.java @@ -0,0 +1,19 @@ +package analyzer.exercises.leap; + +import analyzer.Comment; +import analyzer.CommentType; + +/** + * @see Markdown Template + */ +class NoBuiltInMethods extends Comment { + @Override + public String getKey() { + return "java.leap.no_built_in_methods"; + } + + @Override + public CommentType getType() { + return CommentType.ESSENTIAL; + } +} diff --git a/src/main/java/analyzer/exercises/leap/UseMinimumNumberOfChecks.java b/src/main/java/analyzer/exercises/leap/UseMinimumNumberOfChecks.java new file mode 100644 index 00000000..7a909b0a --- /dev/null +++ b/src/main/java/analyzer/exercises/leap/UseMinimumNumberOfChecks.java @@ -0,0 +1,19 @@ +package analyzer.exercises.leap; + +import analyzer.Comment; +import analyzer.CommentType; + +/** + * @see Markdown Template + */ +class UseMinimumNumberOfChecks extends Comment { + @Override + public String getKey() { + return "java.leap.use_minimum_number_of_checks"; + } + + @Override + public CommentType getType() { + return CommentType.ACTIONABLE; + } +} diff --git a/tests/leap/hard-coded-test-cases/.meta/config.json b/tests/leap/hard-coded-test-cases/.meta/config.json new file mode 100644 index 00000000..d977953c --- /dev/null +++ b/tests/leap/hard-coded-test-cases/.meta/config.json @@ -0,0 +1,29 @@ +{ + "authors": [ + "sonapraneeth-a" + ], + "contributors": [ + "jmrunkle", + "lemoncurry", + "msomji", + "muzimuzhi", + "sshine" + ], + "files": { + "solution": [ + "src/main/java/Leap.java" + ], + "test": [ + "src/test/java/LeapTest.java" + ], + "example": [ + ".meta/src/reference/java/Leap.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Determine whether a given year is a leap year.", + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://coderanch.com/t/718816/Leap" +} \ No newline at end of file diff --git a/tests/leap/hard-coded-test-cases/expected_analysis.json b/tests/leap/hard-coded-test-cases/expected_analysis.json new file mode 100644 index 00000000..e4934cb5 --- /dev/null +++ b/tests/leap/hard-coded-test-cases/expected_analysis.json @@ -0,0 +1,14 @@ +{"comments": [ + { + "comment": "java.general.avoid_hard_coded_test_cases", + "type": "essential" + }, + { + "comment": "java.leap.use_minimum_number_of_checks", + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "type": "informative" + } +]} \ No newline at end of file diff --git a/tests/leap/hard-coded-test-cases/expected_tags.json b/tests/leap/hard-coded-test-cases/expected_tags.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tests/leap/hard-coded-test-cases/expected_tags.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/leap/hard-coded-test-cases/src/main/java/Leap.java b/tests/leap/hard-coded-test-cases/src/main/java/Leap.java new file mode 100644 index 00000000..84e26b16 --- /dev/null +++ b/tests/leap/hard-coded-test-cases/src/main/java/Leap.java @@ -0,0 +1,5 @@ +class Leap { + boolean isLeapYear(int year) { + return year == 1960 || year == 2000 || year == 2400 || year == 1996; + } +} diff --git a/tests/leap/optimal-solution/.meta/config.json b/tests/leap/optimal-solution/.meta/config.json new file mode 100644 index 00000000..d977953c --- /dev/null +++ b/tests/leap/optimal-solution/.meta/config.json @@ -0,0 +1,29 @@ +{ + "authors": [ + "sonapraneeth-a" + ], + "contributors": [ + "jmrunkle", + "lemoncurry", + "msomji", + "muzimuzhi", + "sshine" + ], + "files": { + "solution": [ + "src/main/java/Leap.java" + ], + "test": [ + "src/test/java/LeapTest.java" + ], + "example": [ + ".meta/src/reference/java/Leap.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Determine whether a given year is a leap year.", + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://coderanch.com/t/718816/Leap" +} \ No newline at end of file diff --git a/tests/leap/optimal-solution/expected_analysis.json b/tests/leap/optimal-solution/expected_analysis.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tests/leap/optimal-solution/expected_analysis.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/leap/optimal-solution/expected_tags.json b/tests/leap/optimal-solution/expected_tags.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tests/leap/optimal-solution/expected_tags.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/leap/optimal-solution/src/main/java/Leap.java b/tests/leap/optimal-solution/src/main/java/Leap.java new file mode 100644 index 00000000..2987c90a --- /dev/null +++ b/tests/leap/optimal-solution/src/main/java/Leap.java @@ -0,0 +1,5 @@ +class Leap { + boolean isLeapYear(int year) { + return year % 400 == 0 || year % 100 != 0 && year % 4 == 0; + } +} diff --git a/tests/leap/using-gregorian-calendar/.meta/config.json b/tests/leap/using-gregorian-calendar/.meta/config.json new file mode 100644 index 00000000..d977953c --- /dev/null +++ b/tests/leap/using-gregorian-calendar/.meta/config.json @@ -0,0 +1,29 @@ +{ + "authors": [ + "sonapraneeth-a" + ], + "contributors": [ + "jmrunkle", + "lemoncurry", + "msomji", + "muzimuzhi", + "sshine" + ], + "files": { + "solution": [ + "src/main/java/Leap.java" + ], + "test": [ + "src/test/java/LeapTest.java" + ], + "example": [ + ".meta/src/reference/java/Leap.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Determine whether a given year is a leap year.", + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://coderanch.com/t/718816/Leap" +} \ No newline at end of file diff --git a/tests/leap/using-gregorian-calendar/expected_analysis.json b/tests/leap/using-gregorian-calendar/expected_analysis.json new file mode 100644 index 00000000..d7466506 --- /dev/null +++ b/tests/leap/using-gregorian-calendar/expected_analysis.json @@ -0,0 +1,10 @@ +{"comments": [ + { + "comment": "java.leap.no_built_in_methods", + "type": "essential" + }, + { + "comment": "java.general.feedback_request", + "type": "informative" + } +]} \ No newline at end of file diff --git a/tests/leap/using-gregorian-calendar/expected_tags.json b/tests/leap/using-gregorian-calendar/expected_tags.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tests/leap/using-gregorian-calendar/expected_tags.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/leap/using-gregorian-calendar/src/main/java/Leap.java b/tests/leap/using-gregorian-calendar/src/main/java/Leap.java new file mode 100644 index 00000000..8f5c80b1 --- /dev/null +++ b/tests/leap/using-gregorian-calendar/src/main/java/Leap.java @@ -0,0 +1,9 @@ +import java.util.Calendar; +import java.util.GregorianCalendar; + +class Leap { + boolean isLeapYear(int year) { + var calendar = new GregorianCalendar(Calendar.ALL_STYLES); + return calendar.isLeapYear(year); + } +} \ No newline at end of file diff --git a/tests/leap/using-if-statements/.meta/config.json b/tests/leap/using-if-statements/.meta/config.json new file mode 100644 index 00000000..d977953c --- /dev/null +++ b/tests/leap/using-if-statements/.meta/config.json @@ -0,0 +1,29 @@ +{ + "authors": [ + "sonapraneeth-a" + ], + "contributors": [ + "jmrunkle", + "lemoncurry", + "msomji", + "muzimuzhi", + "sshine" + ], + "files": { + "solution": [ + "src/main/java/Leap.java" + ], + "test": [ + "src/test/java/LeapTest.java" + ], + "example": [ + ".meta/src/reference/java/Leap.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Determine whether a given year is a leap year.", + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://coderanch.com/t/718816/Leap" +} \ No newline at end of file diff --git a/tests/leap/using-if-statements/expected_analysis.json b/tests/leap/using-if-statements/expected_analysis.json new file mode 100644 index 00000000..f09b4a58 --- /dev/null +++ b/tests/leap/using-if-statements/expected_analysis.json @@ -0,0 +1,10 @@ +{"comments": [ + { + "comment": "java.leap.avoid_conditional_logic", + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "type": "informative" + } +]} \ No newline at end of file diff --git a/tests/leap/using-if-statements/expected_tags.json b/tests/leap/using-if-statements/expected_tags.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tests/leap/using-if-statements/expected_tags.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/leap/using-if-statements/src/main/java/Leap.java b/tests/leap/using-if-statements/src/main/java/Leap.java new file mode 100644 index 00000000..939da788 --- /dev/null +++ b/tests/leap/using-if-statements/src/main/java/Leap.java @@ -0,0 +1,8 @@ +class Leap { + boolean isLeapYear(int year) { + if (year % 400 == 0) return true; + if (year % 100 == 0) return false; + if (year % 4 == 0) return true; + return false; + } +} \ No newline at end of file diff --git a/tests/leap/using-java-time/.meta/config.json b/tests/leap/using-java-time/.meta/config.json new file mode 100644 index 00000000..d977953c --- /dev/null +++ b/tests/leap/using-java-time/.meta/config.json @@ -0,0 +1,29 @@ +{ + "authors": [ + "sonapraneeth-a" + ], + "contributors": [ + "jmrunkle", + "lemoncurry", + "msomji", + "muzimuzhi", + "sshine" + ], + "files": { + "solution": [ + "src/main/java/Leap.java" + ], + "test": [ + "src/test/java/LeapTest.java" + ], + "example": [ + ".meta/src/reference/java/Leap.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Determine whether a given year is a leap year.", + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://coderanch.com/t/718816/Leap" +} \ No newline at end of file diff --git a/tests/leap/using-java-time/expected_analysis.json b/tests/leap/using-java-time/expected_analysis.json new file mode 100644 index 00000000..d7466506 --- /dev/null +++ b/tests/leap/using-java-time/expected_analysis.json @@ -0,0 +1,10 @@ +{"comments": [ + { + "comment": "java.leap.no_built_in_methods", + "type": "essential" + }, + { + "comment": "java.general.feedback_request", + "type": "informative" + } +]} \ No newline at end of file diff --git a/tests/leap/using-java-time/expected_tags.json b/tests/leap/using-java-time/expected_tags.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tests/leap/using-java-time/expected_tags.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/leap/using-java-time/src/main/java/Leap.java b/tests/leap/using-java-time/src/main/java/Leap.java new file mode 100644 index 00000000..e3957535 --- /dev/null +++ b/tests/leap/using-java-time/src/main/java/Leap.java @@ -0,0 +1,7 @@ +import java.time.Year; + +class Leap { + boolean isLeapYear(int year) { + return Year.isLeap(year); + } +} diff --git a/tests/leap/using-ternary/.meta/config.json b/tests/leap/using-ternary/.meta/config.json new file mode 100644 index 00000000..d977953c --- /dev/null +++ b/tests/leap/using-ternary/.meta/config.json @@ -0,0 +1,29 @@ +{ + "authors": [ + "sonapraneeth-a" + ], + "contributors": [ + "jmrunkle", + "lemoncurry", + "msomji", + "muzimuzhi", + "sshine" + ], + "files": { + "solution": [ + "src/main/java/Leap.java" + ], + "test": [ + "src/test/java/LeapTest.java" + ], + "example": [ + ".meta/src/reference/java/Leap.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Determine whether a given year is a leap year.", + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://coderanch.com/t/718816/Leap" +} \ No newline at end of file diff --git a/tests/leap/using-ternary/expected_analysis.json b/tests/leap/using-ternary/expected_analysis.json new file mode 100644 index 00000000..f09b4a58 --- /dev/null +++ b/tests/leap/using-ternary/expected_analysis.json @@ -0,0 +1,10 @@ +{"comments": [ + { + "comment": "java.leap.avoid_conditional_logic", + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "type": "informative" + } +]} \ No newline at end of file diff --git a/tests/leap/using-ternary/expected_tags.json b/tests/leap/using-ternary/expected_tags.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tests/leap/using-ternary/expected_tags.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/leap/using-ternary/src/main/java/Leap.java b/tests/leap/using-ternary/src/main/java/Leap.java new file mode 100644 index 00000000..c6a484c9 --- /dev/null +++ b/tests/leap/using-ternary/src/main/java/Leap.java @@ -0,0 +1,5 @@ +class Leap { + boolean isLeapYear(int year) { + return (year % 400 == 0) ? true : (year % 100 == 0) ? false : (year % 4 == 0) ? true : false; + } +} \ No newline at end of file diff --git a/tests/leap/using-too-many-checks/.meta/config.json b/tests/leap/using-too-many-checks/.meta/config.json new file mode 100644 index 00000000..d977953c --- /dev/null +++ b/tests/leap/using-too-many-checks/.meta/config.json @@ -0,0 +1,29 @@ +{ + "authors": [ + "sonapraneeth-a" + ], + "contributors": [ + "jmrunkle", + "lemoncurry", + "msomji", + "muzimuzhi", + "sshine" + ], + "files": { + "solution": [ + "src/main/java/Leap.java" + ], + "test": [ + "src/test/java/LeapTest.java" + ], + "example": [ + ".meta/src/reference/java/Leap.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Determine whether a given year is a leap year.", + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://coderanch.com/t/718816/Leap" +} \ No newline at end of file diff --git a/tests/leap/using-too-many-checks/expected_analysis.json b/tests/leap/using-too-many-checks/expected_analysis.json new file mode 100644 index 00000000..55786b70 --- /dev/null +++ b/tests/leap/using-too-many-checks/expected_analysis.json @@ -0,0 +1,10 @@ +{"comments": [ + { + "comment": "java.leap.use_minimum_number_of_checks", + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "type": "informative" + } +]} \ No newline at end of file diff --git a/tests/leap/using-too-many-checks/expected_tags.json b/tests/leap/using-too-many-checks/expected_tags.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tests/leap/using-too-many-checks/expected_tags.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/leap/using-too-many-checks/src/main/java/Leap.java b/tests/leap/using-too-many-checks/src/main/java/Leap.java new file mode 100644 index 00000000..ce273627 --- /dev/null +++ b/tests/leap/using-too-many-checks/src/main/java/Leap.java @@ -0,0 +1,5 @@ +class Leap { + boolean isLeapYear(int year) { + return year % 4 == 0 && year % 100 != 0 || year % 100 == 0 && year % 400 == 0; + } +}