Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ dependencies {
implementation(project(":azure-intellij-plugin-lib-java"))
implementation("com.microsoft.azure:azure-toolkit-common-lib")
implementation("com.microsoft.azure:azure-toolkit-ide-common-lib")
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
testImplementation("org.mockito:mockito-core:3.9.0")

intellijPlatform {
// Plugin Dependencies. Uses `platformBundledPlugins` property from the gradle.properties file for bundled IntelliJ Platform plugins.
Expand All @@ -11,4 +13,8 @@ dependencies {
bundledPlugin("org.jetbrains.idea.maven.model")
bundledPlugin("com.intellij.gradle")
}

tasks.named("test", Test::class) {
useJUnitPlatform()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# **Azure Toolkit for IntelliJ: Java SDK Integration**

The **Azure Toolkit for IntelliJ** is a project designed to empower Java developers by simplifying the creation, configuration, and usage of Azure services directly within IntelliJ IDEA. This plugin enhances productivity by providing seamless access to Azure SDKs and integrates a **Code Quality Analyzer Tool Window** that offers continuous analysis, real-time code suggestions, to improve Java code quality.

## **Features**
- **Imported Rule Sets**: The plugin integrates with Azure SDKs to provide real-time code suggestions and best practices.
- **Code Quality Analyzer**: The tool window offers continuous analysis and recommendations to improve Java code quality.

## **Integrated Rule Sets**

### **Messaging**

#### **1. Prefer ServiceBusProcessorClient over ServiceBusReceiverAsyncClient**
- **Anti-pattern**: Using the low-level `ServiceBusReceiverAsyncClient` API, which requires advanced Reactive programming skills.
- **Issue**: Increased complexity and potential misuse by non-experts in Reactive paradigms.
- **Severity**: WARNING
- **Recommendation**: Use the higher-level `ServiceBusProcessorClient` for simplified and efficient message handling.
[Learn more](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/servicebus/azure-messaging-servicebus/README.md#when-to-use-servicebusprocessorclient).

#### **2. Explicitly Disable Auto-Complete in ServiceBus Clients**
- **Anti-pattern**: Relying on default auto-completion without explicit verification.
- **Issue**: Messages may be incorrectly marked as completed even after processing failures.
- **Severity**: WARNING
- **Recommendation**: Use `disableAutoComplete()` to control message completion explicitly. See the [Azure ServiceBus documentation](https://learn.microsoft.com/java/api/com.azure.messaging.servicebus.servicebusclientbuilder.servicebusreceiverclientbuilder-disableautocomplete) for guidance.

#### **3. Prefer EventProcessorClient over EventHubConsumerAsyncClient**
- **Anti-pattern**: Use of low level “EventHubConsumerAsyncClient” useful only when building a Reactive library or
an end-to-end Reactive application.
- **Issue**: Increased complexity and potential misuse by non-experts in Reactive paradigms.
- **Severity**: WARNING
- **Recommendation**: Use the higher-level `EventProcessorClient` for efficient and reliable event processing.
[Learn more](https://learn.microsoft.com/azure/service-bus-messaging/service-bus-prefetch?tabs=dotnet#why-is-prefetch-not-the-default-option).

#### **4. Use EventProcessorClient for Checkpoint Management**
- **Anti-pattern**: Calling `updateCheckpointAsync()` without proper blocking (`block()`).
- **Issue**: Results in ineffective checkpoint updates.
- **Severity**: WARNING
- **Recommendation**: Ensure the `block()` operator is used with an appropriate timeout for reliable checkpoint updates.

---

### **Identity**

#### **5. Avoid Hardcoded API Keys and Tokens**
- **Anti-pattern**: Storing sensitive credentials in source code.
- **Issue**: Exposes credentials to security breaches.
- **Severity**: WARNING
- **Recommendation**: `DefaultAzureCredential` is recommended for authentication. If not, then use environment variables when using key based authentication.
[Learn more](https://learn.microsoft.com/java/api/com.azure.identity.defaultazurecredential?view=azure-java-stable).

---

### **Async**

#### **6. Use SyncPoller Instead of PollerFlux#getSyncPoller()**
- **Anti-pattern**: Converting asynchronous polling to synchronous with `getSyncPoller()`.
- **Issue**: Adds unnecessary complexity and blocking on asynchronous operation.
- **Severity**: WARNING
- **Recommendation**: Use `SyncPoller` directly for synchronous operations.
[Learn more](https://learn.microsoft.com/java/api/com.azure.core.util.polling.syncpoller?view=azure-java-stable).

---

### **Storage**

#### **7. Storage Upload without Length Check**
- **Anti-pattern**: Using Azure Storage upload APIs without a length parameter, causing the entire data payload to buffer in memory.
- **Issue**: Risks `OutOfMemoryErrors` for large files or high-volume uploads.
- **Severity**: INFO
- **Recommendation**: Use APIs that accept a length parameter. Refer to the [Azure SDK for Java documentation](https://learn.microsoft.com/azure/storage/blobs/storage-blob-upload-java) for details.

---

### **Performance Optimization**

#### **8. Avoid Dynamic Client Creation**
- **Anti-pattern**: Creating new client instances for each operation instead of reusing them.
- **Issue**: Leads to resource overhead, reduced performance, and increased latency.
- **Severity**: WARNING
- **Recommendation**: Reuse client instances throughout the application's lifecycle.
[Learn more](https://learn.microsoft.com/azure/developer/java/sdk/overview#connect-to-and-use-azure-resources-with-client-libraries).

#### **9. Batch Operations Instead of Single Operations in Loops**
- **Anti-pattern**: Performing repetitive single operations instead of batch processing.
- **Issue**: Inefficient resource use and slower execution.
- **Severity**: WARNING
- **Recommendation**: Utilize batch APIs for optimized resource usage.

---

#### **10. Recommended Alternatives for Common APIs**
- **Authentication**: Use `DefaultAzureCredential` over connection strings.
- **Azure OpenAI**: Prefer `getChatCompletions` for conversational AI instead of `getCompletions`.
[Learn more](https://learn.microsoft.com/java/api/overview/azure/ai-openai-readme?view=azure-java-preview).

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.azure.toolkit.intellij.java.sdk.analyzer;

import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiMethodCallExpression;
import com.microsoft.azure.toolkit.intellij.java.sdk.models.RuleConfig;
import com.microsoft.azure.toolkit.intellij.java.sdk.utils.HelperUtils;
import com.microsoft.azure.toolkit.intellij.java.sdk.utils.RuleConfigLoader;
import java.util.Map;
import org.jetbrains.annotations.NotNull;

/**
* Inspection tool to check discouraged Connection String usage.
*/
public class ConnectionStringCheck extends LocalInspectionTool {

@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new ConnectionStringCheckVisitor(holder, RuleConfigLoader.getInstance().getRuleConfigs());
}

static class ConnectionStringCheckVisitor extends JavaElementVisitor {
private final ProblemsHolder holder;
private static RuleConfig RULE_CONFIG;
private static boolean SKIP_WHOLE_RULE;

ConnectionStringCheckVisitor(ProblemsHolder holder, Map<String, RuleConfig> ruleConfigs) {
this.holder = holder;
initializeRuleConfig(ruleConfigs);
}

private void initializeRuleConfig(Map<String, RuleConfig> ruleConfigs) {
if (RULE_CONFIG == null) {
final String ruleName = "ConnectionStringCheck";
RULE_CONFIG = ruleConfigs.get(ruleName);
SKIP_WHOLE_RULE = RULE_CONFIG.isSkipRuleCheck();
}
}

@Override
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
super.visitMethodCallExpression(expression);
// Check if the rule should be skipped
if (SKIP_WHOLE_RULE) {
return;
}

PsiMethod method = HelperUtils.getResolvedMethod(expression);
if (HelperUtils.isItDiscouragedAPI(method, RULE_CONFIG.getUsagesToCheck(), RULE_CONFIG.getScopeToCheck())) {

PsiElement problemElement = expression.getMethodExpression().getReferenceNameElement();
if (problemElement != null) {
holder.registerProblem(problemElement, RULE_CONFIG.getAntiPatternMessage());
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.azure.toolkit.intellij.java.sdk.analyzer;

import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiDeclarationStatement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiVariable;
import com.microsoft.azure.toolkit.intellij.java.sdk.models.RuleConfig;
import com.microsoft.azure.toolkit.intellij.java.sdk.utils.HelperUtils;
import com.microsoft.azure.toolkit.intellij.java.sdk.utils.RuleConfigLoader;
import java.util.Map;
import org.jetbrains.annotations.NotNull;

/**
* This class extends the LocalInspectionTool and is used to inspect the usage of Azure SDK ServiceBusReceiver &
* ServiceBusProcessor clients in the code. It checks if the auto-complete feature is disabled for the clients. If the
* auto-complete feature is not disabled, a problem is registered with the ProblemsHolder.
*/
public class DisableAutoCompleteCheck extends LocalInspectionTool {

/**
* Build the visitor for the inspection. This visitor will be used to traverse the PSI tree.
*
* @param holder The holder for the problems found
*
* @return The visitor for the inspection. This is not used anywhere else in the code.
*/
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new DisableAutoCompleteVisitor(holder, RuleConfigLoader.getInstance().getRuleConfigs());
}

/**
* This class extends the JavaElementVisitor and is used to visit the Java elements in the code. It checks for the
* usage of Azure SDK ServiceBusReceiver & ServiceBusProcessor clients and whether the auto-complete feature is
* disabled. If the auto-complete feature is not disabled, a problem is registered with the ProblemsHolder.
*/
static class DisableAutoCompleteVisitor extends JavaElementVisitor {

private static RuleConfig RULE_CONFIG;
private static boolean SKIP_WHOLE_RULE;
private final ProblemsHolder holder;

DisableAutoCompleteVisitor(ProblemsHolder holder, Map<String, RuleConfig> ruleConfigs) {
this.holder = holder;
initializeRuleConfig(ruleConfigs);
}

private void initializeRuleConfig(Map<String, RuleConfig> ruleConfigs) {
if (RULE_CONFIG == null) {
final String ruleName = "DisableAutoCompleteCheck";
RULE_CONFIG = ruleConfigs.get(ruleName);
SKIP_WHOLE_RULE = RULE_CONFIG.isSkipRuleCheck();
}
}
/**
* This method is used to visit the declaration statements in the code. It checks for the declaration of Azure
* SDK ServiceBusReceiver & ServiceBusProcessor clients and whether the auto-complete feature is disabled. If
* the auto-complete feature is not disabled, a problem is registered with the ProblemsHolder.
*
* @param statement The declaration statement to visit
*/
@Override
public void visitDeclarationStatement(PsiDeclarationStatement statement) {

if (SKIP_WHOLE_RULE) {
return;
}
super.visitDeclarationStatement(statement);

// Get the declared elements
PsiElement[] elements = statement.getDeclaredElements();

// Get the variable declaration
if (elements.length > 0 && elements[0] instanceof PsiVariable) {
PsiVariable variable = (PsiVariable) elements[0];

// Process the variable declaration
processVariableDeclaration(variable);
}
}

/**
* This method is used to process the variable declaration. It checks for the declaration of Azure SDK
* ServiceBusReceiver & ServiceBusProcessor clients and whether the auto-complete feature is disabled. If the
* auto-complete feature is not disabled, a problem is registered with the ProblemsHolder.
*
* @param variable The variable to process
*/
private void processVariableDeclaration(PsiVariable variable) {

// Retrieve the client name (left side of the declaration)
PsiType clientType = variable.getType();

// Check the assignment part (right side)
PsiExpression initializer = variable.getInitializer();

// Check if the client type is an Azure SDK client
if (HelperUtils.isAzurePackage(clientType.getCanonicalText())) {
if (HelperUtils.checkIfInScope(RULE_CONFIG.getScopeToCheck(), clientType.getPresentableText())) {
if (initializer instanceof PsiMethodCallExpression) {
if (!isAutoCompleteDisabled((PsiMethodCallExpression) initializer)) {
holder.registerProblem(initializer, RULE_CONFIG.getAntiPatternMessage());
}
}
}
}
}

/**
* This method is used to check if the auto-complete feature is disabled. It iterates up the chain of method
* calls to check if the auto-complete feature is disabled.
*
* @param methodCallExpression The method call expression to check
*
* @return true if the auto-complete feature is disabled, false otherwise
*/
private static boolean isAutoCompleteDisabled(PsiMethodCallExpression methodCallExpression) {

// Iterating up the chain of method calls
PsiExpression qualifier = methodCallExpression.getMethodExpression().getQualifierExpression();

// Check if the method call chain has the method to check
while (qualifier instanceof PsiMethodCallExpression) {

if (qualifier instanceof PsiMethodCallExpression) {

// Get the method expression
PsiReferenceExpression methodExpression =
((PsiMethodCallExpression) qualifier).getMethodExpression();

// Get the method name
String methodName = methodExpression.getReferenceName();

// Check if the method name is the method to check
if (RULE_CONFIG.getUsagesToCheck().contains(methodName)) {
return true;
}
}
qualifier = ((PsiMethodCallExpression) qualifier).getMethodExpression().getQualifierExpression();
}
// When the chain has been traversed and the method to check is not found
return false;
}
}
}
Loading