Skip to content

Commit

Permalink
Leap analyzer (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
sanderploegsma authored Jan 22, 2024
1 parent 65325ac commit 1104f8b
Show file tree
Hide file tree
Showing 35 changed files with 487 additions and 15 deletions.
10 changes: 5 additions & 5 deletions bin/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@
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
fi
done
done

exit ${exit_code}
exit ${exit_code}
2 changes: 2 additions & 0 deletions src/main/java/analyzer/AnalyzerRoot.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -30,6 +31,7 @@ private static List<Analyzer> createAnalyzers(String slug) {

switch (slug) {
case "hamming" -> analyzers.add(new HammingAnalyzer());
case "leap" -> analyzers.add(new LeapAnalyzer());
case "two-fer" -> analyzers.add(new TwoferAnalyzer());
}

Expand Down
18 changes: 8 additions & 10 deletions src/main/java/analyzer/exercises/GlobalAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,31 @@

import java.util.List;

public class GlobalAnalyzer extends VoidVisitorAdapter<Void> implements Analyzer {
private Analysis analysis;
public class GlobalAnalyzer extends VoidVisitorAdapter<Analysis> implements Analyzer {

@Override
public void analyze(List<CompilationUnit> 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) {
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/analyzer/exercises/leap/AvoidConditionalLogic.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package analyzer.exercises.leap;

import analyzer.Comment;
import analyzer.CommentType;

/**
* @see <a href="https://github.com/exercism/website-copy/blob/main/analyzer-comments/java/leap/avoid_conditional_logic.md">Markdown Template</a>
*/
class AvoidConditionalLogic extends Comment {
@Override
public String getKey() {
return "java.leap.avoid_conditional_logic";
}

@Override
public CommentType getType() {
return CommentType.ACTIONABLE;
}
}
96 changes: 96 additions & 0 deletions src/main/java/analyzer/exercises/leap/LeapAnalyzer.java
Original file line number Diff line number Diff line change
@@ -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<Analysis> implements Analyzer {
private static final Set<Integer> TEST_CASES = Set.of(1960, 1996, 2000, 2400);
private static final Set<String> DISALLOWED_IMPORTS = Set.of(
"java.time",
"java.util.GregorianCalendar"
);

private final Set<Integer> intLiterals = new HashSet<>();

@Override
public void analyze(List<CompilationUnit> 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;
}
}
19 changes: 19 additions & 0 deletions src/main/java/analyzer/exercises/leap/NoBuiltInMethods.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package analyzer.exercises.leap;

import analyzer.Comment;
import analyzer.CommentType;

/**
* @see <a href="https://github.com/exercism/website-copy/blob/main/analyzer-comments/java/leap/no_built_in_methods.md">Markdown Template</a>
*/
class NoBuiltInMethods extends Comment {
@Override
public String getKey() {
return "java.leap.no_built_in_methods";
}

@Override
public CommentType getType() {
return CommentType.ESSENTIAL;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package analyzer.exercises.leap;

import analyzer.Comment;
import analyzer.CommentType;

/**
* @see <a href="https://github.com/exercism/website-copy/blob/main/analyzer-comments/java/leap/use_minimum_number_of_checks.md">Markdown Template</a>
*/
class UseMinimumNumberOfChecks extends Comment {
@Override
public String getKey() {
return "java.leap.use_minimum_number_of_checks";
}

@Override
public CommentType getType() {
return CommentType.ACTIONABLE;
}
}
29 changes: 29 additions & 0 deletions tests/leap/hard-coded-test-cases/.meta/config.json
Original file line number Diff line number Diff line change
@@ -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"
}
14 changes: 14 additions & 0 deletions tests/leap/hard-coded-test-cases/expected_analysis.json
Original file line number Diff line number Diff line change
@@ -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"
}
]}
1 change: 1 addition & 0 deletions tests/leap/hard-coded-test-cases/expected_tags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions tests/leap/hard-coded-test-cases/src/main/java/Leap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Leap {
boolean isLeapYear(int year) {
return year == 1960 || year == 2000 || year == 2400 || year == 1996;
}
}
29 changes: 29 additions & 0 deletions tests/leap/optimal-solution/.meta/config.json
Original file line number Diff line number Diff line change
@@ -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"
}
1 change: 1 addition & 0 deletions tests/leap/optimal-solution/expected_analysis.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions tests/leap/optimal-solution/expected_tags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions tests/leap/optimal-solution/src/main/java/Leap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Leap {
boolean isLeapYear(int year) {
return year % 400 == 0 || year % 100 != 0 && year % 4 == 0;
}
}
29 changes: 29 additions & 0 deletions tests/leap/using-gregorian-calendar/.meta/config.json
Original file line number Diff line number Diff line change
@@ -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"
}
10 changes: 10 additions & 0 deletions tests/leap/using-gregorian-calendar/expected_analysis.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{"comments": [
{
"comment": "java.leap.no_built_in_methods",
"type": "essential"
},
{
"comment": "java.general.feedback_request",
"type": "informative"
}
]}
1 change: 1 addition & 0 deletions tests/leap/using-gregorian-calendar/expected_tags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
9 changes: 9 additions & 0 deletions tests/leap/using-gregorian-calendar/src/main/java/Leap.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit 1104f8b

Please sign in to comment.