Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiReferenceExpression;

import com.microsoft.azure.toolkit.intellij.azure.sdk.buildtool.replaceaction.ReplaceElementQuickFix;
import org.jetbrains.annotations.NotNull;

/**
Expand Down Expand Up @@ -112,7 +113,7 @@ public void visitElement(@NotNull PsiElement element) {
return;
}
// give the suggestion of the discouraged method
holder.registerProblem(problemElement, RULE_CONFIG.getAntiPatternMessageMap().get(method.getName()));
holder.registerProblem(problemElement, RULE_CONFIG.getAntiPatternMessageMap().get(method.getName()), new ReplaceElementQuickFix(problemElement, RULE_CONFIG.getReplacementMap().get(method.getName())));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiTypeElement;
import com.microsoft.azure.toolkit.intellij.azure.sdk.buildtool.replaceaction.ReplaceElementQuickFix;
import org.jetbrains.annotations.NotNull;

/**
Expand All @@ -18,9 +19,6 @@ public class DetectDiscouragedClientCheck extends LocalInspectionTool {
/**
* This method builds a visitor to check for the discouraged client name in the code.
* If the client name matches the discouraged client, a problem is registered with the suggestion message.
*
* @param holder - the ProblemsHolder object to register the problem
* @param isOnTheFly - whether the inspection is on the fly - not used in this implementation but required by the parent class
*/
@NotNull
@Override
Expand Down Expand Up @@ -81,7 +79,8 @@ public void visitTypeElement(PsiTypeElement element) {
}

// Register a problem if the client used matches a discouraged client
holder.registerProblem(element, RULE_CONFIG.getAntiPatternMessageMap().get(elementType));
holder.registerProblem(element, RULE_CONFIG.getAntiPatternMessageMap().get(elementType),
new ReplaceElementQuickFix(element, RULE_CONFIG.getReplacementMap().get(elementType)));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
import com.intellij.psi.PsiStatement;
import org.jetbrains.annotations.NotNull;

import java.util.List;

/**
* This class is used to check for the dynamic creation of clients in the code.
* It extends the LocalInspectionTool class, which is used to create custom code inspections.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiIdentifier;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiNewExpression;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiVariable;
import com.microsoft.azure.toolkit.intellij.azure.sdk.buildtool.replaceaction.ReplaceElementQuickFix;
import org.jetbrains.annotations.NotNull;

/**
Expand Down Expand Up @@ -89,7 +91,15 @@ public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expressio
// Using KeyCredential indicates authentication of a non-Azure OpenAI client
// If the endpoint method is used with KeyCredential for non-Azure OpenAI clients, a warning is registered
if (isUsingKeyCredential(expression)) {
holder.registerProblem(expression, RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage"));

// Get the endpoint method identifier and the qualifier expression
PsiIdentifier endpointMethod = (PsiIdentifier) methodExpression.getReferenceNameElement();
PsiExpression qualifier = expression.getMethodExpression().getQualifierExpression();

if (endpointMethod == null || qualifier == null) {
return;
}
holder.registerProblem(endpointMethod, RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage"), new ReplaceElementQuickFix(expression, qualifier.getText()));
}
}
}
Expand Down Expand Up @@ -146,6 +156,10 @@ private static boolean isKeyCredential(PsiExpression expression) {
// Cast the element to a new expression
PsiNewExpression newExpression = (PsiNewExpression) expression;

if (newExpression.getClassReference() == null) {
return false;
}

// Get the class reference name from the new expression
String classReference = newExpression.getClassReference().getReferenceName();
if (classReference != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.psi.xml.XmlFile;
import com.microsoft.azure.toolkit.intellij.azure.sdk.buildtool.replaceaction.ReplaceElementQuickFix;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
Expand Down Expand Up @@ -74,9 +75,11 @@ protected void checkAndFlagVersion(String fullName, String currentVersion, Probl
if (!encounteredVersionGroup.equals(versionGroup) && encounteredVersionGroup.startsWith(versionGroup.substring(0, versionGroup.lastIndexOf("_")))) {
String recommendedVersion = encounteredVersionGroup.substring(encounteredVersionGroup.lastIndexOf("_") + 1);

String replacementTagText = "<version>" + recommendedVersion + ".x</version>";

// Flag the version if the minor version is different from the recommended version
String message = getFormattedMessage(fullName, recommendedVersion, IncompatibleDependencyVisitor.RULE_CONFIG);
holder.registerProblem(versionElement, message);
holder.registerProblem(versionElement, message, new ReplaceElementQuickFix(versionElement, replacementTagText));
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,28 @@ class RuleConfig {
private final List<String> servicesToCheck;
private final Map<String, String> antiPatternMessageMap;
private final List<String> listedItemsToCheck;
private final Map<String, String> replacementMap;

static final String AZURE_PACKAGE_NAME = "com.azure";

static final RuleConfig EMPTY_RULE = new RuleConfig(Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyMap(), Collections.emptyList());
static final RuleConfig EMPTY_RULE = new RuleConfig(Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), Collections.emptyMap());

/**
* Constructor for RuleConfig.
*
* @param methodsToCheck List of methods to check.
* @param clientsToCheck List of clients to check.
* @param servicesToCheck List of services to check.
* @param antiPatternMessageMap Map of antipattern messages to display.
* @param listedItemsToCheck List of items to check for.
* @param methodsToCheck List of methods to check.
* @param clientsToCheck List of clients to check.
* @param servicesToCheck List of services to check.
* @param antiPatternMessageMap Map of antipattern messages to display.
* @param listedItemsToCheck List of items to check for.
*/
RuleConfig(List<String> methodsToCheck, List<String> clientsToCheck, List<String> servicesToCheck, Map<String, String> antiPatternMessageMap, List<String> listedItemsToCheck) {
RuleConfig(List<String> methodsToCheck, List<String> clientsToCheck, List<String> servicesToCheck, Map<String, String> antiPatternMessageMap, List<String> listedItemsToCheck, Map<String, String> replacementMap) {
this.methodsToCheck = methodsToCheck;
this.clientsToCheck = clientsToCheck;
this.servicesToCheck = servicesToCheck;
this.antiPatternMessageMap = antiPatternMessageMap;
this.listedItemsToCheck = listedItemsToCheck;
this.replacementMap = replacementMap;
}

/**
Expand Down Expand Up @@ -95,4 +97,13 @@ Map<String, String> getAntiPatternMessageMap() {
List<String> getListedItemsToCheck() {
return listedItemsToCheck;
}

/**
* This method returns the replacementMap string for the rule
*
* @return replacementMap The replacementMap string for the rule
*/
Map<String, String> getReplacementMap() {
return replacementMap;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ private RuleConfig getRuleConfig(JsonReader reader) throws IOException {
List<String> servicesToCheck = new ArrayList<>();
Map<String, String> antiPatternMessageMap = new HashMap<>();
List<String> listedItemsToCheck = new ArrayList<>();
Map<String, String> replacementMap = new HashMap<>();

// Check if the JSON file starts with an object
if (reader.nextToken() != JsonToken.START_OBJECT) {
Expand All @@ -149,7 +150,7 @@ private RuleConfig getRuleConfig(JsonReader reader) throws IOException {
methodsToCheck = getListFromJsonArray(reader);
break;
case "antiPatternMessage":
antiPatternMessageMap = getMapFromJsonObject(reader, antiPatternMessageMap);
antiPatternMessageMap = getMapFromJsonObject(reader, antiPatternMessageMap, replacementMap);
break;
case "clientsToCheck":
clientsToCheck = getListFromJsonArray(reader);
Expand All @@ -166,18 +167,21 @@ private RuleConfig getRuleConfig(JsonReader reader) throws IOException {
case "url":
listedItemsToCheck = getListFromJsonArray(reader);
break;
case "replacement":
replacementMap = getMapFromJsonObject(reader, antiPatternMessageMap, replacementMap);
break;
default:
if (fieldName.endsWith("Check")) {
// Move to the next token to process the nested object
reader.nextToken();
antiPatternMessageMap = getMapFromJsonObject(reader, antiPatternMessageMap);
antiPatternMessageMap = getMapFromJsonObject(reader, antiPatternMessageMap, replacementMap);
} else {
reader.skipChildren();
}
break;
}
}
return new RuleConfig(methodsToCheck, clientsToCheck, servicesToCheck, antiPatternMessageMap, listedItemsToCheck);
return new RuleConfig(methodsToCheck, clientsToCheck, servicesToCheck, antiPatternMessageMap, listedItemsToCheck, replacementMap);
}

/**
Expand Down Expand Up @@ -219,10 +223,11 @@ private List<String> getListFromJsonArray(JsonReader reader) throws IOException
* @return Map of strings parsed from the JSON object
* @throws IOException - if there is an error reading the file
*/
private Map<String, String> getMapFromJsonObject(JsonReader reader, Map<String, String> antiPatternMessageMap) throws IOException {
private Map<String, String> getMapFromJsonObject(JsonReader reader, Map<String, String> antiPatternMessageMap, Map<String, String> replacementMap) throws IOException {

String identifiersToCheck = null;
String antiPatternMessage = null;
String replacement = null;

// Read the JSON file and parse the RuleConfig object
while (reader.nextToken() != JsonToken.END_OBJECT) {
Expand All @@ -237,6 +242,9 @@ private Map<String, String> getMapFromJsonObject(JsonReader reader, Map<String,
case "antiPatternMessage":
antiPatternMessage = reader.getString();
break;
case "replacement":
replacement = reader.getString();
break;
default:
reader.skipChildren();
break;
Expand All @@ -253,9 +261,29 @@ private Map<String, String> getMapFromJsonObject(JsonReader reader, Map<String,
return antiPatternMessageMap;
}
}

// Add to map based on conditions
if (replacement != null) {

// This map is for base classes that have a set of discouraged identifiers and a corresponding set of recommendations
if (identifiersToCheck != null) {
replacementMap.put(identifiersToCheck, replacement);
} else {
// This map is for single recommendations of a particular rule
replacementMap.put(fieldName, replacement);
return replacementMap;
}
}
}

// This section is to return mapped anti-pattern messages or replacement messages
if (antiPatternMessage != null) {
return antiPatternMessageMap;
}
if (replacement != null) {
return replacementMap;
}
// This map is to return mapped anti-pattern messages that have a set of discouraged identifiers and a corresponding set of antipattern messages
return antiPatternMessageMap;
return new HashMap<>();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.intellij.psi.PsiWhileStatement;
import com.intellij.psi.util.PsiTreeUtil;

import com.microsoft.azure.toolkit.intellij.azure.sdk.buildtool.replaceaction.ReplaceElementQuickFix;
import org.jetbrains.annotations.NotNull;

/**
Expand Down Expand Up @@ -170,12 +171,12 @@ private boolean checkLoopForTextAnalyticsClientOperation(PsiStatement loopStatem

// Check if the statement is an expression statement and is an Azure client operation
if (statement instanceof PsiExpressionStatement) {
isExpressionAzureClientOperation(statement);
isExpressionAzureClientOperation(statement, loopStatement);
}

// Check if the statement is a declaration statement and is an Azure client operation
if (statement instanceof PsiDeclarationStatement) {
isDeclarationAzureClientOperation((PsiDeclarationStatement) statement);
isDeclarationAzureClientOperation((PsiDeclarationStatement) statement, loopStatement);
}
}
return true;
Expand Down Expand Up @@ -204,7 +205,7 @@ private static PsiStatement getLoopBody(PsiStatement loopStatement) {
*
* @param statement The statement to check
*/
private void isExpressionAzureClientOperation(PsiStatement statement) {
private void isExpressionAzureClientOperation(PsiStatement statement, PsiStatement loopStatement) {

// Get the expression from the statement
PsiExpression expression = ((PsiExpressionStatement) statement).getExpression();
Expand All @@ -213,9 +214,12 @@ private void isExpressionAzureClientOperation(PsiStatement statement) {
// Check if the expression is an Azure client operation
if (isAzureTextAnalyticsClientOperation((PsiMethodCallExpression) expression)) {

// The qualifier is the variable that the method is called on
PsiElement qualifier = ((PsiMethodCallExpression) expression).getMethodExpression().getQualifier();

// get the method name
String methodName = ((PsiMethodCallExpression) expression).getMethodExpression().getReferenceName();
holder.registerProblem(expression, (RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage") + methodName + "Batch"));
holder.registerProblem(expression, (RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage") + methodName + "Batch"), new ReplaceElementQuickFix(loopStatement, qualifier.getText() + "." + methodName + "Batch()"));
}
}
}
Expand All @@ -225,7 +229,7 @@ private void isExpressionAzureClientOperation(PsiStatement statement) {
*
* @param statement The declaration statement to check
*/
private void isDeclarationAzureClientOperation(PsiDeclarationStatement statement) {
private void isDeclarationAzureClientOperation(PsiDeclarationStatement statement, PsiStatement loopStatement) {

// getDeclaredElements() returns the variables declared in the statement
for (PsiElement element : statement.getDeclaredElements()) {
Expand All @@ -241,9 +245,13 @@ private void isDeclarationAzureClientOperation(PsiDeclarationStatement statement
}
// Check if the initializer is an Azure client operation
if (isAzureTextAnalyticsClientOperation((PsiMethodCallExpression) initializer)) {

// The qualifier is the variable that the method is called on
PsiElement qualifier = ((PsiMethodCallExpression) initializer).getMethodExpression().getQualifier();

// get the method name
String methodName = ((PsiMethodCallExpression) initializer).getMethodExpression().getReferenceName();
holder.registerProblem(initializer, (RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage") + methodName + "Batch"));
holder.registerProblem(initializer, (RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage") + methodName + "Batch"), new ReplaceElementQuickFix(loopStatement, qualifier.getText() + "." + methodName + "Batch()"));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiMethodCallExpression;
import com.microsoft.azure.toolkit.intellij.azure.sdk.buildtool.replaceaction.ReplaceElementQuickFix;
import org.jetbrains.annotations.NotNull;

/**
Expand Down Expand Up @@ -86,7 +87,7 @@ public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expressio
// Check if the following method is not `block` or `block_with_timeout` and
// Check if the updateCheckpointAsync() method call is called on an EventBatchContext object
if ((followingMethod == null || !RULE_CONFIG.getMethodsToCheck().contains(followingMethod)) && isCalledOnEventBatchContext(expression)) {
holder.registerProblem(expression, RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage"));
holder.registerProblem(expression, RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage"), new ReplaceElementQuickFix(expression, expression.getText() + "block()"));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiMethodCallExpression;
import com.microsoft.azure.toolkit.intellij.azure.sdk.buildtool.replaceaction.ReplaceElementQuickFix;
import org.jetbrains.annotations.NotNull;

/**
Expand Down Expand Up @@ -86,7 +87,7 @@ public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expressio
// Check if the following method is `subscribe` and
// Check if the updateCheckpointAsync() method call is called on an EventBatchContext object
if (RULE_CONFIG.getMethodsToCheck().get(0).equals(followingMethod) && isCalledOnEventBatchContext(expression)) {
holder.registerProblem(expression, RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage"));
holder.registerProblem(expression, RULE_CONFIG.getAntiPatternMessageMap().get("antiPatternMessage"), new ReplaceElementQuickFix(expression, expression.getText() + "block()"));
}
}
}
Expand Down
Loading