Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Leap analyzer #86

Merged
merged 11 commits into from
Jan 22, 2024
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
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