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

Implement analyzer for secrets #141

Merged
merged 7 commits into from
Mar 11, 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
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());
manumafe98 marked this conversation as resolved.
Show resolved Hide resolved
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();
}
}
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