Skip to content

Commit d87d4ce

Browse files
Merge pull request #9654 from samvaity/develop
Refactor Java Code Quality Analyzer
2 parents de67602 + 595e161 commit d87d4ce

32 files changed

+3865
-12
lines changed

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-java-sdk/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ dependencies {
33
implementation(project(":azure-intellij-plugin-lib-java"))
44
implementation("com.microsoft.azure:azure-toolkit-common-lib")
55
implementation("com.microsoft.azure:azure-toolkit-ide-common-lib")
6+
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
7+
testImplementation("org.mockito:mockito-core:3.9.0")
68

79
intellijPlatform {
810
// Plugin Dependencies. Uses `platformBundledPlugins` property from the gradle.properties file for bundled IntelliJ Platform plugins.
@@ -11,4 +13,8 @@ dependencies {
1113
bundledPlugin("org.jetbrains.idea.maven.model")
1214
bundledPlugin("com.intellij.gradle")
1315
}
16+
17+
tasks.named("test", Test::class) {
18+
useJUnitPlatform()
19+
}
1420
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# **Azure Toolkit for IntelliJ: Java SDK Integration**
2+
3+
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.
4+
5+
## **Features**
6+
- **Imported Rule Sets**: The plugin integrates with Azure SDKs to provide real-time code suggestions and best practices.
7+
- **Code Quality Analyzer**: The tool window offers continuous analysis and recommendations to improve Java code quality.
8+
9+
## **Integrated Rule Sets**
10+
11+
### **Messaging**
12+
13+
#### **1. Prefer ServiceBusProcessorClient over ServiceBusReceiverAsyncClient**
14+
- **Anti-pattern**: Using the low-level `ServiceBusReceiverAsyncClient` API, which requires advanced Reactive programming skills.
15+
- **Issue**: Increased complexity and potential misuse by non-experts in Reactive paradigms.
16+
- **Severity**: WARNING
17+
- **Recommendation**: Use the higher-level `ServiceBusProcessorClient` for simplified and efficient message handling.
18+
[Learn more](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/servicebus/azure-messaging-servicebus/README.md#when-to-use-servicebusprocessorclient).
19+
20+
#### **2. Explicitly Disable Auto-Complete in ServiceBus Clients**
21+
- **Anti-pattern**: Relying on default auto-completion without explicit verification.
22+
- **Issue**: Messages may be incorrectly marked as completed even after processing failures.
23+
- **Severity**: WARNING
24+
- **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.
25+
26+
#### **3. Prefer EventProcessorClient over EventHubConsumerAsyncClient**
27+
- **Anti-pattern**: Use of low level “EventHubConsumerAsyncClient” useful only when building a Reactive library or
28+
an end-to-end Reactive application.
29+
- **Issue**: Increased complexity and potential misuse by non-experts in Reactive paradigms.
30+
- **Severity**: WARNING
31+
- **Recommendation**: Use the higher-level `EventProcessorClient` for efficient and reliable event processing.
32+
[Learn more](https://learn.microsoft.com/azure/service-bus-messaging/service-bus-prefetch?tabs=dotnet#why-is-prefetch-not-the-default-option).
33+
34+
#### **4. Use EventProcessorClient for Checkpoint Management**
35+
- **Anti-pattern**: Calling `updateCheckpointAsync()` without proper blocking (`block()`).
36+
- **Issue**: Results in ineffective checkpoint updates.
37+
- **Severity**: WARNING
38+
- **Recommendation**: Ensure the `block()` operator is used with an appropriate timeout for reliable checkpoint updates.
39+
40+
---
41+
42+
### **Identity**
43+
44+
#### **5. Avoid Hardcoded API Keys and Tokens**
45+
- **Anti-pattern**: Storing sensitive credentials in source code.
46+
- **Issue**: Exposes credentials to security breaches.
47+
- **Severity**: WARNING
48+
- **Recommendation**: `DefaultAzureCredential` is recommended for authentication. If not, then use environment variables when using key based authentication.
49+
[Learn more](https://learn.microsoft.com/java/api/com.azure.identity.defaultazurecredential?view=azure-java-stable).
50+
51+
---
52+
53+
### **Async**
54+
55+
#### **6. Use SyncPoller Instead of PollerFlux#getSyncPoller()**
56+
- **Anti-pattern**: Converting asynchronous polling to synchronous with `getSyncPoller()`.
57+
- **Issue**: Adds unnecessary complexity and blocking on asynchronous operation.
58+
- **Severity**: WARNING
59+
- **Recommendation**: Use `SyncPoller` directly for synchronous operations.
60+
[Learn more](https://learn.microsoft.com/java/api/com.azure.core.util.polling.syncpoller?view=azure-java-stable).
61+
62+
---
63+
64+
### **Storage**
65+
66+
#### **7. Storage Upload without Length Check**
67+
- **Anti-pattern**: Using Azure Storage upload APIs without a length parameter, causing the entire data payload to buffer in memory.
68+
- **Issue**: Risks `OutOfMemoryErrors` for large files or high-volume uploads.
69+
- **Severity**: INFO
70+
- **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.
71+
72+
---
73+
74+
### **Performance Optimization**
75+
76+
#### **8. Avoid Dynamic Client Creation**
77+
- **Anti-pattern**: Creating new client instances for each operation instead of reusing them.
78+
- **Issue**: Leads to resource overhead, reduced performance, and increased latency.
79+
- **Severity**: WARNING
80+
- **Recommendation**: Reuse client instances throughout the application's lifecycle.
81+
[Learn more](https://learn.microsoft.com/azure/developer/java/sdk/overview#connect-to-and-use-azure-resources-with-client-libraries).
82+
83+
#### **9. Batch Operations Instead of Single Operations in Loops**
84+
- **Anti-pattern**: Performing repetitive single operations instead of batch processing.
85+
- **Issue**: Inefficient resource use and slower execution.
86+
- **Severity**: WARNING
87+
- **Recommendation**: Utilize batch APIs for optimized resource usage.
88+
89+
---
90+
91+
#### **10. Recommended Alternatives for Common APIs**
92+
- **Authentication**: Use `DefaultAzureCredential` over connection strings.
93+
- **Azure OpenAI**: Prefer `getChatCompletions` for conversational AI instead of `getCompletions`.
94+
[Learn more](https://learn.microsoft.com/java/api/overview/azure/ai-openai-readme?view=azure-java-preview).
95+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.azure.toolkit.intellij.java.sdk.analyzer;
5+
6+
import com.intellij.codeInspection.LocalInspectionTool;
7+
import com.intellij.codeInspection.ProblemsHolder;
8+
import com.intellij.psi.JavaElementVisitor;
9+
import com.intellij.psi.PsiElement;
10+
import com.intellij.psi.PsiElementVisitor;
11+
import com.intellij.psi.PsiMethod;
12+
import com.intellij.psi.PsiMethodCallExpression;
13+
import com.microsoft.azure.toolkit.intellij.java.sdk.models.RuleConfig;
14+
import com.microsoft.azure.toolkit.intellij.java.sdk.utils.HelperUtils;
15+
import com.microsoft.azure.toolkit.intellij.java.sdk.utils.RuleConfigLoader;
16+
import java.util.Map;
17+
import org.jetbrains.annotations.NotNull;
18+
19+
/**
20+
* Inspection tool to check discouraged Connection String usage.
21+
*/
22+
public class ConnectionStringCheck extends LocalInspectionTool {
23+
24+
@Override
25+
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
26+
return new ConnectionStringCheckVisitor(holder, RuleConfigLoader.getInstance().getRuleConfigs());
27+
}
28+
29+
static class ConnectionStringCheckVisitor extends JavaElementVisitor {
30+
private final ProblemsHolder holder;
31+
private static RuleConfig RULE_CONFIG;
32+
private static boolean SKIP_WHOLE_RULE;
33+
34+
ConnectionStringCheckVisitor(ProblemsHolder holder, Map<String, RuleConfig> ruleConfigs) {
35+
this.holder = holder;
36+
initializeRuleConfig(ruleConfigs);
37+
}
38+
39+
private void initializeRuleConfig(Map<String, RuleConfig> ruleConfigs) {
40+
if (RULE_CONFIG == null) {
41+
final String ruleName = "ConnectionStringCheck";
42+
RULE_CONFIG = ruleConfigs.get(ruleName);
43+
SKIP_WHOLE_RULE = RULE_CONFIG.isSkipRuleCheck();
44+
}
45+
}
46+
47+
@Override
48+
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
49+
super.visitMethodCallExpression(expression);
50+
// Check if the rule should be skipped
51+
if (SKIP_WHOLE_RULE) {
52+
return;
53+
}
54+
55+
PsiMethod method = HelperUtils.getResolvedMethod(expression);
56+
if (HelperUtils.isItDiscouragedAPI(method, RULE_CONFIG.getUsagesToCheck(), RULE_CONFIG.getScopeToCheck())) {
57+
58+
PsiElement problemElement = expression.getMethodExpression().getReferenceNameElement();
59+
if (problemElement != null) {
60+
holder.registerProblem(problemElement, RULE_CONFIG.getAntiPatternMessage());
61+
}
62+
}
63+
}
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.azure.toolkit.intellij.java.sdk.analyzer;
5+
6+
import com.intellij.codeInspection.LocalInspectionTool;
7+
import com.intellij.codeInspection.ProblemsHolder;
8+
import com.intellij.psi.JavaElementVisitor;
9+
import com.intellij.psi.PsiDeclarationStatement;
10+
import com.intellij.psi.PsiElement;
11+
import com.intellij.psi.PsiElementVisitor;
12+
import com.intellij.psi.PsiExpression;
13+
import com.intellij.psi.PsiMethodCallExpression;
14+
import com.intellij.psi.PsiReferenceExpression;
15+
import com.intellij.psi.PsiType;
16+
import com.intellij.psi.PsiVariable;
17+
import com.microsoft.azure.toolkit.intellij.java.sdk.models.RuleConfig;
18+
import com.microsoft.azure.toolkit.intellij.java.sdk.utils.HelperUtils;
19+
import com.microsoft.azure.toolkit.intellij.java.sdk.utils.RuleConfigLoader;
20+
import java.util.Map;
21+
import org.jetbrains.annotations.NotNull;
22+
23+
/**
24+
* This class extends the LocalInspectionTool and is used to inspect the usage of Azure SDK ServiceBusReceiver &
25+
* ServiceBusProcessor clients in the code. It checks if the auto-complete feature is disabled for the clients. If the
26+
* auto-complete feature is not disabled, a problem is registered with the ProblemsHolder.
27+
*/
28+
public class DisableAutoCompleteCheck extends LocalInspectionTool {
29+
30+
/**
31+
* Build the visitor for the inspection. This visitor will be used to traverse the PSI tree.
32+
*
33+
* @param holder The holder for the problems found
34+
*
35+
* @return The visitor for the inspection. This is not used anywhere else in the code.
36+
*/
37+
@NotNull
38+
@Override
39+
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
40+
return new DisableAutoCompleteVisitor(holder, RuleConfigLoader.getInstance().getRuleConfigs());
41+
}
42+
43+
/**
44+
* This class extends the JavaElementVisitor and is used to visit the Java elements in the code. It checks for the
45+
* usage of Azure SDK ServiceBusReceiver & ServiceBusProcessor clients and whether the auto-complete feature is
46+
* disabled. If the auto-complete feature is not disabled, a problem is registered with the ProblemsHolder.
47+
*/
48+
static class DisableAutoCompleteVisitor extends JavaElementVisitor {
49+
50+
private static RuleConfig RULE_CONFIG;
51+
private static boolean SKIP_WHOLE_RULE;
52+
private final ProblemsHolder holder;
53+
54+
DisableAutoCompleteVisitor(ProblemsHolder holder, Map<String, RuleConfig> ruleConfigs) {
55+
this.holder = holder;
56+
initializeRuleConfig(ruleConfigs);
57+
}
58+
59+
private void initializeRuleConfig(Map<String, RuleConfig> ruleConfigs) {
60+
if (RULE_CONFIG == null) {
61+
final String ruleName = "DisableAutoCompleteCheck";
62+
RULE_CONFIG = ruleConfigs.get(ruleName);
63+
SKIP_WHOLE_RULE = RULE_CONFIG.isSkipRuleCheck();
64+
}
65+
}
66+
/**
67+
* This method is used to visit the declaration statements in the code. It checks for the declaration of Azure
68+
* SDK ServiceBusReceiver & ServiceBusProcessor clients and whether the auto-complete feature is disabled. If
69+
* the auto-complete feature is not disabled, a problem is registered with the ProblemsHolder.
70+
*
71+
* @param statement The declaration statement to visit
72+
*/
73+
@Override
74+
public void visitDeclarationStatement(PsiDeclarationStatement statement) {
75+
76+
if (SKIP_WHOLE_RULE) {
77+
return;
78+
}
79+
super.visitDeclarationStatement(statement);
80+
81+
// Get the declared elements
82+
PsiElement[] elements = statement.getDeclaredElements();
83+
84+
// Get the variable declaration
85+
if (elements.length > 0 && elements[0] instanceof PsiVariable) {
86+
PsiVariable variable = (PsiVariable) elements[0];
87+
88+
// Process the variable declaration
89+
processVariableDeclaration(variable);
90+
}
91+
}
92+
93+
/**
94+
* This method is used to process the variable declaration. It checks for the declaration of Azure SDK
95+
* ServiceBusReceiver & ServiceBusProcessor clients and whether the auto-complete feature is disabled. If the
96+
* auto-complete feature is not disabled, a problem is registered with the ProblemsHolder.
97+
*
98+
* @param variable The variable to process
99+
*/
100+
private void processVariableDeclaration(PsiVariable variable) {
101+
102+
// Retrieve the client name (left side of the declaration)
103+
PsiType clientType = variable.getType();
104+
105+
// Check the assignment part (right side)
106+
PsiExpression initializer = variable.getInitializer();
107+
108+
// Check if the client type is an Azure SDK client
109+
if (HelperUtils.isAzurePackage(clientType.getCanonicalText())) {
110+
if (HelperUtils.checkIfInScope(RULE_CONFIG.getScopeToCheck(), clientType.getPresentableText())) {
111+
if (initializer instanceof PsiMethodCallExpression) {
112+
if (!isAutoCompleteDisabled((PsiMethodCallExpression) initializer)) {
113+
holder.registerProblem(initializer, RULE_CONFIG.getAntiPatternMessage());
114+
}
115+
}
116+
}
117+
}
118+
}
119+
120+
/**
121+
* This method is used to check if the auto-complete feature is disabled. It iterates up the chain of method
122+
* calls to check if the auto-complete feature is disabled.
123+
*
124+
* @param methodCallExpression The method call expression to check
125+
*
126+
* @return true if the auto-complete feature is disabled, false otherwise
127+
*/
128+
private static boolean isAutoCompleteDisabled(PsiMethodCallExpression methodCallExpression) {
129+
130+
// Iterating up the chain of method calls
131+
PsiExpression qualifier = methodCallExpression.getMethodExpression().getQualifierExpression();
132+
133+
// Check if the method call chain has the method to check
134+
while (qualifier instanceof PsiMethodCallExpression) {
135+
136+
if (qualifier instanceof PsiMethodCallExpression) {
137+
138+
// Get the method expression
139+
PsiReferenceExpression methodExpression =
140+
((PsiMethodCallExpression) qualifier).getMethodExpression();
141+
142+
// Get the method name
143+
String methodName = methodExpression.getReferenceName();
144+
145+
// Check if the method name is the method to check
146+
if (RULE_CONFIG.getUsagesToCheck().contains(methodName)) {
147+
return true;
148+
}
149+
}
150+
qualifier = ((PsiMethodCallExpression) qualifier).getMethodExpression().getQualifierExpression();
151+
}
152+
// When the chain has been traversed and the method to check is not found
153+
return false;
154+
}
155+
}
156+
}

0 commit comments

Comments
 (0)