Skip to content

Commit

Permalink
Implement analyzer for secrets (#141)
Browse files Browse the repository at this point in the history
* Adding Secrets Analyzer with Comments

* Adding analyzer tests

* Adding smoke tests

* Fixing smoke tests indenting and prefer bitwise not comment javadoc

* Applying suggestions
Udating Analyzer root to run the analyzer on alphabetical order
Changing implement operator comment to use bitwise operator
Updating the analyzer to only return 1 essential comment at a time
Generalizing the method that checks the usage of the operator
Adding extra comment to check for conditional logic
Updating preferBitwiseNot to trigger if the student used bitwise and

* Adding extra scenario to match all essential comments

* Updting visit analyzer method to only add one essential commit
  • Loading branch information
manumafe98 authored Mar 11, 2024
1 parent 14e4f15 commit 136383d
Show file tree
Hide file tree
Showing 38 changed files with 700 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/main/java/analyzer/AnalyzerRoot.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import analyzer.exercises.leap.LeapAnalyzer;
import analyzer.exercises.loglevels.LogLevelsAnalyzer;
import analyzer.exercises.needforspeed.NeedForSpeedAnalyzer;
import analyzer.exercises.secrets.SecretsAnalyzer;
import analyzer.exercises.twofer.TwoferAnalyzer;

import java.util.ArrayList;
Expand Down Expand Up @@ -54,6 +55,7 @@ private static List<Analyzer> createAnalyzers(String slug) {
case "leap" -> analyzers.add(new LeapAnalyzer());
case "log-levels" -> analyzers.add(new LogLevelsAnalyzer());
case "need-for-speed" -> analyzers.add(new NeedForSpeedAnalyzer());
case "secrets" -> analyzers.add(new SecretsAnalyzer());
case "two-fer" -> analyzers.add(new TwoferAnalyzer());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package analyzer.exercises.secrets;

import analyzer.Comment;

/**
* @see <a href="https://github.com/exercism/website-copy/blob/main/analyzer-comments/java/secrets/avoid_conditional_logic.md">Markdown Template</a>
*/
class AvoidConditionalLogic extends Comment {

@Override
public String getKey() {
return "java.secrets.avoid_conditional_logic";
}

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

import analyzer.Comment;

/**
* @see <a href="https://github.com/exercism/website-copy/blob/main/analyzer-comments/java/secrets/prefer_bitwise_not.md">Markdown Template</a>
*/
class PreferBitwiseNot extends Comment {

@Override
public String getKey() {
return "java.secrets.prefer_bitwise_not";
}

@Override
public Type getType() {
return Type.INFORMATIVE;
}
}
94 changes: 94 additions & 0 deletions src/main/java/analyzer/exercises/secrets/SecretsAnalyzer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package analyzer.exercises.secrets;

import com.github.javaparser.ast.CompilationUnit;
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.UnaryExpr;
import com.github.javaparser.ast.stmt.IfStmt;
import com.github.javaparser.ast.stmt.Statement;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;

import analyzer.Analyzer;
import analyzer.OutputCollector;
import analyzer.Solution;
import analyzer.comments.ExemplarSolution;

/**
* The {@link SecretsAnalyzer} is the analyzer implementation for the {@code secrets} practice exercise.
* It extends from the {@link VoidVisitorAdapter} and uses the visitor pattern to traverse each compilation unit.
*
* @see <a href="https://github.com/exercism/java/tree/main/exercises/concept/secrets">The secrets exercise on the Java track</a>
*/
public class SecretsAnalyzer extends VoidVisitorAdapter<OutputCollector> implements Analyzer {
private static final String EXERCISE_NAME = "Secrets";
private static final String SHIFT_BACK = "shiftBack";
private static final String SET_BITS = "setBits";
private static final String FLIP_BITS = "flipBits";
private static final String CLEAR_BITS = "clearBits";
private boolean essentialCommentAdded = false;

@Override
public void analyze(Solution solution, OutputCollector output) {
for (CompilationUnit compilationUnit : solution.getCompilationUnits()) {
compilationUnit.accept(this, output);
}

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

@Override
public void visit(MethodDeclaration node, OutputCollector output) {

if (!essentialCommentAdded && node.getNameAsString().equals(SHIFT_BACK) && doesNotUseOperator(node, BinaryExpr.Operator.UNSIGNED_RIGHT_SHIFT)) {
output.addComment(new UseBitwiseOperator(">>>", SHIFT_BACK));
essentialCommentAdded = true;
}

if (!essentialCommentAdded && node.getNameAsString().equals(SET_BITS) && doesNotUseOperator(node, BinaryExpr.Operator.BINARY_OR)) {
output.addComment(new UseBitwiseOperator("|", SET_BITS));
essentialCommentAdded = true;
}

if (!essentialCommentAdded && node.getNameAsString().equals(FLIP_BITS) && doesNotUseOperator(node, BinaryExpr.Operator.XOR)) {
output.addComment(new UseBitwiseOperator("^", FLIP_BITS));
essentialCommentAdded = true;
}

if (!essentialCommentAdded && node.getNameAsString().equals(CLEAR_BITS) && doesNotUseOperator(node, BinaryExpr.Operator.BINARY_AND)) {
output.addComment(new UseBitwiseOperator("&", CLEAR_BITS));
essentialCommentAdded = true;
}

if (!essentialCommentAdded && node.getNameAsString().equals(CLEAR_BITS) && !doesNotUseOperator(node, BinaryExpr.Operator.BINARY_AND) && doesNotImplementBitwiseNot(node)) {
output.addComment(new PreferBitwiseNot());
}

if (!essentialCommentAdded && hasConditional(node)) {
output.addComment(new AvoidConditionalLogic());
}

super.visit(node, output);
}

private static boolean doesNotUseOperator(MethodDeclaration node, BinaryExpr.Operator operator) {
return node.findAll(BinaryExpr.class, x -> x.getOperator() == operator).isEmpty();
}

private static boolean doesNotImplementBitwiseNot(MethodDeclaration node) {
return node.findAll(UnaryExpr.class, x -> x.getOperator() == UnaryExpr.Operator.BITWISE_COMPLEMENT).isEmpty();
}

private static boolean hasConditional(MethodDeclaration node) {
return node.getBody()
.map(body -> body.getStatements().stream()
.anyMatch(SecretsAnalyzer::isConditionalExpresion))
.orElse(false);
}

private static boolean isConditionalExpresion(Statement statement) {
return !statement.findAll(IfStmt.class).isEmpty() || !statement.findAll(ConditionalExpr.class).isEmpty();
}
}
36 changes: 36 additions & 0 deletions src/main/java/analyzer/exercises/secrets/UseBitwiseOperator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package analyzer.exercises.secrets;

import analyzer.Comment;

import java.util.Map;

/**
* @see <a href="https://github.com/exercism/website-copy/blob/main/analyzer-comments/java/secrets/use_bitwise_operator.md">Markdown Template</a>
*/
class UseBitwiseOperator extends Comment {
private final String operatorToUse;
private final String calledMethod;

public UseBitwiseOperator(String operatorToUse, String calledMethod) {
this.operatorToUse = operatorToUse;
this.calledMethod = calledMethod;
}

@Override
public String getKey() {
return "java.secrets.use_bitwise_operator";
}

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

@Override
public Type getType() {
return Type.ESSENTIAL;
}
}
19 changes: 19 additions & 0 deletions src/test/java/analyzer/AnalyzerIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,23 @@ void loglevels(String scenario) throws IOException {

Approvals.verify(serialize(output.analysis()), Approvals.NAMES.withParameters(scenario));
}

@ParameterizedTest
@ValueSource(strings = {
"ExemplarSolution",
"NotUsingBitwiseAnd",
"NotUsingBitwiseNot",
"NotUsingBitwiseOr",
"NotUsingBitwiseXor",
"NotUsingUnsignedRightShift",
"UsingIfStatement",
"NotUsingAnyOfTheExpectedOperators"
})
void secrets(String scenario) throws IOException {
var path = Path.of("secrets", scenario + ".java");
var solution = new SolutionFromFiles("secrets", SCENARIOS.resolve(path));
var output = AnalyzerRoot.analyze(solution);

Approvals.verify(serialize(output.analysis()), Approvals.NAMES.withParameters(scenario));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"comments": [
{
"comment": "java.general.exemplar",
"params": {
"exerciseName": "Secrets"
},
"type": "celebratory"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"comments": [
{
"comment": "java.secrets.use_bitwise_operator",
"params": {
"calledMethod": "shiftBack",
"operatorToUse": "\u003e\u003e\u003e"
},
"type": "essential"
},
{
"comment": "java.general.feedback_request",
"params": {},
"type": "informative"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"comments": [
{
"comment": "java.secrets.use_bitwise_operator",
"params": {
"calledMethod": "clearBits",
"operatorToUse": "\u0026"
},
"type": "essential"
},
{
"comment": "java.general.feedback_request",
"params": {},
"type": "informative"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"comments": [
{
"comment": "java.secrets.prefer_bitwise_not",
"params": {},
"type": "informative"
},
{
"comment": "java.general.feedback_request",
"params": {},
"type": "informative"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"comments": [
{
"comment": "java.secrets.use_bitwise_operator",
"params": {
"calledMethod": "setBits",
"operatorToUse": "|"
},
"type": "essential"
},
{
"comment": "java.general.feedback_request",
"params": {},
"type": "informative"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"comments": [
{
"comment": "java.secrets.use_bitwise_operator",
"params": {
"calledMethod": "flipBits",
"operatorToUse": "^"
},
"type": "essential"
},
{
"comment": "java.general.feedback_request",
"params": {},
"type": "informative"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"comments": [
{
"comment": "java.secrets.use_bitwise_operator",
"params": {
"calledMethod": "shiftBack",
"operatorToUse": "\u003e\u003e\u003e"
},
"type": "essential"
},
{
"comment": "java.general.feedback_request",
"params": {},
"type": "informative"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"comments": [
{
"comment": "java.secrets.avoid_conditional_logic",
"params": {},
"type": "actionable"
},
{
"comment": "java.general.feedback_request",
"params": {},
"type": "informative"
}
]
}
19 changes: 19 additions & 0 deletions src/test/resources/scenarios/secrets/ExemplarSolution.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package scenarios.secrets;

public class Secrets {
public static int shiftBack(int value, int amount) {
return value >>> amount;
}

public static int setBits(int value, int mask) {
return value | mask;
}

public static int flipBits(int value, int mask) {
return value ^ mask;
}

public static int clearBits(int value, int mask) {
return value & ~mask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package scenarios.secrets;

public class Secrets {
public static int shiftBack(int value, int amount) {
return (value >> amount) & ~(Integer.MIN_VALUE >> (amount - 1));
}

public static int setBits(int value, int mask) {
return value + mask - (value & mask);
}

public static int flipBits(int value, int mask) {
return (value & ~mask) | (~value & mask);
}

public static int clearBits(int value, int mask) {
return ~(~value | mask);
}
}
Loading

0 comments on commit 136383d

Please sign in to comment.