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

Lasagna analyzer #87

Merged
merged 6 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 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.lasagna.LasagnaAnalyzer;
import analyzer.exercises.leap.LeapAnalyzer;
import analyzer.exercises.twofer.TwoferAnalyzer;
import com.github.javaparser.ast.CompilationUnit;
Expand All @@ -19,24 +20,25 @@ public static Analysis analyze(String slug, List<CompilationUnit> compilationUni
analyzer.analyze(compilationUnits, analysis);
}

if (!analysis.getComments().isEmpty()) {
if (analysis.getComments().stream().anyMatch(x -> x.getType() != CommentType.CELEBRATORY)) {
analysis.addComment(new FeedbackRequest());
}

return analysis;
}

private static List<Analyzer> createAnalyzers(String slug) {
var analyzers = new ArrayList<Analyzer>();

analyzers.add(new GlobalAnalyzer());

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

analyzers.add(new GlobalAnalyzer());

return List.copyOf(analyzers);
}
}
32 changes: 32 additions & 0 deletions src/main/java/analyzer/comments/ExemplarSolution.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package analyzer.comments;

import analyzer.Comment;
import analyzer.CommentType;

import java.util.Map;

/**
* @see <a href="https://github.com/exercism/website-copy/blob/main/analyzer-comments/java/general/exemplar.md">Markdown Template</a>
*/
public class ExemplarSolution extends Comment {
private final String exerciseName;

public ExemplarSolution(String exerciseName) {
this.exerciseName = exerciseName;
}

@Override
public String getKey() {
return "java.general.exemplar";
}

@Override
public Map<String, String> getParameters() {
return Map.of("exerciseName", this.exerciseName);
}

@Override
public CommentType getType() {
return CommentType.CELEBRATORY;
}
}
19 changes: 19 additions & 0 deletions src/main/java/analyzer/comments/RemoveTodoComments.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package analyzer.comments;

import analyzer.Comment;
import analyzer.CommentType;

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

@Override
public CommentType getType() {
return CommentType.ACTIONABLE;
}
}
58 changes: 58 additions & 0 deletions src/main/java/analyzer/exercises/lasagna/LasagnaAnalyzer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package analyzer.exercises.lasagna;

import analyzer.Analysis;
import analyzer.Analyzer;
import analyzer.comments.ExemplarSolution;
import analyzer.comments.RemoveTodoComments;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.comments.LineComment;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;

import java.util.List;

public class LasagnaAnalyzer extends VoidVisitorAdapter<Analysis> implements Analyzer {
private static final String EXERCISE_NAME = "Lasagna";
private static final String EXPECTED_MINUTES_IN_OVEN = "expectedMinutesInOven";
private static final String REMAINING_MINUTES_IN_OVEN = "remainingMinutesInOven";
private static final String PREPARATION_TIME_IN_MINUTES = "preparationTimeInMinutes";
private static final String TOTAL_TIME_IN_MINUTES = "totalTimeInMinutes";

@Override
public void analyze(List<CompilationUnit> compilationUnits, Analysis analysis) {
for (CompilationUnit compilationUnit : compilationUnits) {
compilationUnit.accept(this, analysis);
}

if (analysis.getComments().isEmpty()) {
analysis.addComment(new ExemplarSolution(EXERCISE_NAME));
}
}

@Override
public void visit(MethodDeclaration node, Analysis analysis) {
if (node.getNameAsString().equals(REMAINING_MINUTES_IN_OVEN)
&& doesNotCallMethod(node, EXPECTED_MINUTES_IN_OVEN)) {
analysis.addComment(new ReuseCode(REMAINING_MINUTES_IN_OVEN, EXPECTED_MINUTES_IN_OVEN));
}

if (node.getNameAsString().equals(TOTAL_TIME_IN_MINUTES)
&& doesNotCallMethod(node, PREPARATION_TIME_IN_MINUTES)) {
analysis.addComment(new ReuseCode(TOTAL_TIME_IN_MINUTES, PREPARATION_TIME_IN_MINUTES));
}

super.visit(node, analysis);
}

private static boolean doesNotCallMethod(MethodDeclaration node, String otherMethodName) {
return node.findAll(MethodCallExpr.class, x -> x.getNameAsString().contains(otherMethodName)).isEmpty();
}

@Override
public void visit(LineComment node, Analysis analysis) {
if (node.getContent().contains("TODO")) {
analysis.addComment(new RemoveTodoComments());
}
}
}
37 changes: 37 additions & 0 deletions src/main/java/analyzer/exercises/lasagna/ReuseCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package analyzer.exercises.lasagna;

import analyzer.Comment;
import analyzer.CommentType;

import java.util.Map;

/**
* @see <a href="https://github.com/exercism/website-copy/blob/main/analyzer-comments/java/lasagna/reuse_code.md">Markdown Template</a>
*/
class ReuseCode extends Comment {
private final String callingMethod;
private final String methodToCall;

ReuseCode(String callingMethod, String methodToCall) {
this.callingMethod = callingMethod;
this.methodToCall = methodToCall;
}

@Override
public String getKey() {
return "java.lasagna.reuse_code";
}

@Override
public Map<String, String> getParameters() {
return Map.of(
"callingMethod", this.callingMethod,
"methodToCall", this.methodToCall
);
}

@Override
public CommentType getType() {
return CommentType.ACTIONABLE;
}
}
17 changes: 15 additions & 2 deletions src/test/java/analyzer/AnalyzerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;

import java.lang.reflect.InvocationTargetException;
import java.util.List;

public abstract class AnalyzerTest {
protected abstract Analyzer getAnalyzer();
public class AnalyzerTest<T extends Analyzer> {
private final Class<T> analyzerClass;

public AnalyzerTest(Class<T> analyzerClass) {
this.analyzerClass = analyzerClass;
}

protected Analysis analyzeResourceFile(String resourceFileName) {
var resource = getClass().getResourceAsStream(resourceFileName);
Expand All @@ -22,4 +27,12 @@ private Analysis analyze(CompilationUnit compilationUnit) {
getAnalyzer().analyze(List.of(compilationUnit), analysis);
return analysis;
}

private T getAnalyzer() {
try {
return this.analyzerClass.getConstructor().newInstance();
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
throw new AssertionError("Unable to create instance of " + this.analyzerClass.getName(), ex);
}
}
}
8 changes: 3 additions & 5 deletions src/test/java/analyzer/exercises/GlobalAnalyzerTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package analyzer.exercises;

import analyzer.Analyzer;
import analyzer.AnalyzerTest;
import analyzer.comments.AvoidPrintStatements;
import analyzer.comments.DoNotUseMainMethod;
Expand All @@ -11,10 +10,9 @@

import static org.assertj.core.api.Assertions.assertThat;

public class GlobalAnalyzerTest extends AnalyzerTest {
@Override
protected Analyzer getAnalyzer() {
return new GlobalAnalyzer();
public class GlobalAnalyzerTest extends AnalyzerTest<GlobalAnalyzer> {
public GlobalAnalyzerTest() {
super(GlobalAnalyzer.class);
}

@MethodSource
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package analyzer.exercises.hamming;

import analyzer.Analyzer;
import analyzer.Comment;
import analyzer.AnalyzerTest;
import analyzer.Comment;
import analyzer.comments.ConstructorTooLong;
import analyzer.comments.MethodTooLong;
import analyzer.comments.UseProperClassName;
Expand All @@ -15,10 +14,9 @@

import static org.assertj.core.api.Assertions.assertThat;

public class HammingAnalyzerTest extends AnalyzerTest {
@Override
protected Analyzer getAnalyzer() {
return new HammingAnalyzer();
public class HammingAnalyzerTest extends AnalyzerTest<HammingAnalyzer> {
public HammingAnalyzerTest() {
super(HammingAnalyzer.class);
}

private static Stream<Arguments> testCases() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package analyzer.exercises.lasagna;

import analyzer.AnalyzerTest;
import analyzer.Comment;
import analyzer.comments.ExemplarSolution;
import analyzer.comments.RemoveTodoComments;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;

public class LasagnaAnalyzerTest extends AnalyzerTest<LasagnaAnalyzer> {
public LasagnaAnalyzerTest() {
super(LasagnaAnalyzer.class);
}

private static Stream<Arguments> testCases() {
return Stream.of(
Arguments.of("ExemplarSolution.java", List.of(new ExemplarSolution("Lasagna"))),
Arguments.of("ExemplarSolutionWithTodoComments.java", List.of(new RemoveTodoComments())),
Arguments.of("NoReuseOfExpectedMinutesInOven.java",
List.of(new ReuseCode("remainingMinutesInOven", "expectedMinutesInOven"))),
Arguments.of("NoReuseOfPreparationTimeInMinutes.java",
List.of(new ReuseCode("totalTimeInMinutes", "preparationTimeInMinutes"))),
Arguments.of("NoReuseOfBothMethods.java",
List.of(
new ReuseCode("remainingMinutesInOven", "expectedMinutesInOven"),
new ReuseCode("totalTimeInMinutes", "preparationTimeInMinutes")
)
)
);
}

@ParameterizedTest(name = "{0}")
@MethodSource("testCases")
public void testCommentsOnSolution(String filename, List<Comment> expectedComments) {
var analysis = analyzeResourceFile("/analyzer/exercises/lasagna/" + filename);
assertThat(analysis.getComments()).containsExactlyInAnyOrder(expectedComments.toArray(Comment[]::new));
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package analyzer.exercises.twofer;

import analyzer.Analyzer;
import analyzer.Comment;
import analyzer.AnalyzerTest;
import analyzer.Comment;
import analyzer.comments.AvoidHardCodedTestCases;
import analyzer.comments.UseProperClassName;
import analyzer.comments.UseProperMethodName;
Expand All @@ -14,11 +13,10 @@

import static org.assertj.core.api.Assertions.assertThat;

public class TwoferAnalyzerTest extends AnalyzerTest {
public class TwoferAnalyzerTest extends AnalyzerTest<TwoferAnalyzer> {

@Override
protected Analyzer getAnalyzer() {
return new TwoferAnalyzer();
public TwoferAnalyzerTest() {
super(TwoferAnalyzer.class);
}

private static Stream<Arguments> testCases() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
public class Lasagna {
public int expectedMinutesInOven() {
return 40;
}

public int remainingMinutesInOven(int actualMinutesInOven) {
return expectedMinutesInOven() - actualMinutesInOven;
}

public int preparationTimeInMinutes(int numberOfLayers) {
return numberOfLayers * 2;
}

public int totalTimeInMinutes(int numberOfLayers, int actualMinutesInOven) {
return preparationTimeInMinutes(numberOfLayers) + actualMinutesInOven;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
public class Lasagna {
// TODO: define the 'expectedMinutesInOven()' method
public int expectedMinutesInOven() {
return 40;
}

// TODO: define the 'remainingMinutesInOven()' method
public int remainingMinutesInOven(int actualMinutesInOven) {
return expectedMinutesInOven() - actualMinutesInOven;
}

// TODO: define the 'preparationTimeInMinutes()' method
public int preparationTimeInMinutes(int numberOfLayers) {
return numberOfLayers * 2;
}

// TODO: define the 'totalTimeInMinutes()' method
public int totalTimeInMinutes(int numberOfLayers, int actualMinutesInOven) {
return preparationTimeInMinutes(numberOfLayers) + actualMinutesInOven;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
public class Lasagna {
public int expectedMinutesInOven() {
return 40;
}

public int remainingMinutesInOven(int actualMinutesInOven) {
return 40 - actualMinutesInOven;
}

public int preparationTimeInMinutes(int numberOfLayers) {
return numberOfLayers * 2;
}

public int totalTimeInMinutes(int numberOfLayers, int actualMinutesInOven) {
return 2 * numberOfLayers + actualMinutesInOven;
}
}
Loading