mirror of
https://github.com/JetBrains/intellij-sdk-code-samples.git
synced 2025-07-27 08:47:50 +08:00
New topic: Inspection options (#988)
This commit is contained in:
parent
148ce5b74f
commit
4d1b6e6789
44
README.md
44
README.md
@ -34,28 +34,28 @@ Please see [Code Samples][docs:code-samples] topic on how to import and run code
|
|||||||
|
|
||||||
In the following table, you may find all available samples provided in the separated directories as stand-alone projects available for running with the Gradle [`runIde`](tools_gradle_intellij_plugin.md#tasks-runide) task.
|
In the following table, you may find all available samples provided in the separated directories as stand-alone projects available for running with the Gradle [`runIde`](tools_gradle_intellij_plugin.md#tasks-runide) task.
|
||||||
|
|
||||||
| Code Sample | Description |
|
| Code Sample | Description |
|
||||||
|------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| [Action Basics](./action_basics) | Action and Action Group patterns implementation, adds entries to the Tools menu. |
|
| [Action Basics](./action_basics) | Action and Action Group patterns implementation, adds entries to the Tools menu. |
|
||||||
| [Comparing References Inspection](./comparing_references_inspection) | Local Inspection Tool, adds entries to **Settings | Editor | Inspections | Java | Probable Bugs**. |
|
| [Comparing References Inspection](./comparing_string_references_inspection) | Local Inspection Tool, adds entries to **Settings | Editor | Inspections | Java | Probable Bugs**. |
|
||||||
| [Conditional Operator Intention](./conditional_operator_intention) | Intention action, suggests converting a ternary operator into an `if` block and adds entry to **Settings | Editor | Intentions | SDK Intentions**. |
|
| [Conditional Operator Intention](./conditional_operator_intention) | Intention action, suggests converting a ternary operator into an `if` block and adds entry to **Settings | Editor | Intentions | SDK Intentions**. |
|
||||||
| [Editor Basics](./editor_basics) | Basic Editor APIs example with editor popup menu with extra actions. |
|
| [Editor Basics](./editor_basics) | Basic Editor APIs example with editor popup menu with extra actions. |
|
||||||
| [Framework Basics](./framework_basics) | Basic *SDK Demo Framework* support added to the **File | New | Project | Java** wizard. |
|
| [Framework Basics](./framework_basics) | Basic *SDK Demo Framework* support added to the **File | New | Project | Java** wizard. |
|
||||||
| [Kotlin Demo](./kotlin_demo) | Kotlin example extending the *Main Menu* with a **Greeting** menu group. |
|
| [Kotlin Demo](./kotlin_demo) | Kotlin example extending the *Main Menu* with a **Greeting** menu group. |
|
||||||
| [Live Templates](./live_templates) | Live templates for Markdown language, adds an entry to the **Settings | Editor | Live Templates** dialog. |
|
| [Live Templates](./live_templates) | Live templates for Markdown language, adds an entry to the **Settings | Editor | Live Templates** dialog. |
|
||||||
| [Max Opened Projects](./max_opened_projects) | Application services and listeners, shows warning dialog when more than 3 open projects are opened. |
|
| [Max Opened Projects](./max_opened_projects) | Application services and listeners, shows warning dialog when more than 3 open projects are opened. |
|
||||||
| [Module](./module) | *SDK Demo Module* module type added to the **File | New | Project...** wizard. |
|
| [Module](./module) | *SDK Demo Module* module type added to the **File | New | Project...** wizard. |
|
||||||
| [Product Specific - PyCharm Sample](./product_specific/pycharm_basics) | Plugin project configuration for the PyCharm IDE. |
|
| [Product Specific - PyCharm Sample](./product_specific/pycharm_basics) | Plugin project configuration for the PyCharm IDE. |
|
||||||
| [Project Model](./project_model) | Interacts with the project model, adds menu items to **Tools** and **Editor Context** menus. |
|
| [Project Model](./project_model) | Interacts with the project model, adds menu items to **Tools** and **Editor Context** menus. |
|
||||||
| [Project View Pane](./project_view_pane) | Project View Pane listing only image files. |
|
| [Project View Pane](./project_view_pane) | Project View Pane listing only image files. |
|
||||||
| [Project Wizard](./project_wizard) | Project Wizard example with demo steps. |
|
| [Project Wizard](./project_wizard) | Project Wizard example with demo steps. |
|
||||||
| [PSI Demo](./psi_demo) | PSI Navigation features presentation. |
|
| [PSI Demo](./psi_demo) | PSI Navigation features presentation. |
|
||||||
| [Run Configuration](./run_configuration) | Run configuration implementation with factory, options and UI. |
|
| [Run Configuration](./run_configuration) | Run configuration implementation with factory, options and UI. |
|
||||||
| [Settings](./settings) | Custom settings panel, adds a settings panel to the **Settings** panel under **Tools**. |
|
| [Settings](./settings) | Custom settings panel, adds a settings panel to the **Settings** panel under **Tools**. |
|
||||||
| [Simple Language Plugin](./simple_language_plugin) | Custom language support, defines a new *Simple language* with syntax highlighting, annotations, code completion, and other features. |
|
| [Simple Language Plugin](./simple_language_plugin) | Custom language support, defines a new *Simple language* with syntax highlighting, annotations, code completion, and other features. |
|
||||||
| [Theme Basics](./theme_basics) | Sample *theme* plugin with basic interface modifications. |
|
| [Theme Basics](./theme_basics) | Sample *theme* plugin with basic interface modifications. |
|
||||||
| [Tool Window](./tool_window) | Custom Tool Window example plugin. |
|
| [Tool Window](./tool_window) | Custom Tool Window example plugin. |
|
||||||
| [Tree Structure Provider](./tree_structure_provider) | Tree Structure Provider showing only plain text files. |
|
| [Tree Structure Provider](./tree_structure_provider) | Tree Structure Provider showing only plain text files. |
|
||||||
|
|
||||||
[gh:workflow-code-samples]: https://github.com/JetBrains/intellij-sdk-docs/actions/workflows/code-samples.yml
|
[gh:workflow-code-samples]: https://github.com/JetBrains/intellij-sdk-docs/actions/workflows/code-samples.yml
|
||||||
[gh:template]: https://github.com/JetBrains/intellij-platform-plugin-template
|
[gh:template]: https://github.com/JetBrains/intellij-platform-plugin-template
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
rootProject.name = "SDK Code Samples"
|
rootProject.name = "SDK Code Samples"
|
||||||
|
|
||||||
includeBuild("../action_basics")
|
includeBuild("../action_basics")
|
||||||
includeBuild("../comparing_references_inspection")
|
includeBuild("../comparing_string_references_inspection")
|
||||||
includeBuild("../conditional_operator_intention")
|
includeBuild("../conditional_operator_intention")
|
||||||
includeBuild("../editor_basics")
|
includeBuild("../editor_basics")
|
||||||
includeBuild("../facet_basics")
|
includeBuild("../facet_basics")
|
||||||
|
@ -1,203 +0,0 @@
|
|||||||
// Copyright 2000-2022 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package org.intellij.sdk.codeInspection;
|
|
||||||
|
|
||||||
import com.intellij.codeInspection.*;
|
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
|
||||||
import com.intellij.openapi.project.Project;
|
|
||||||
import com.intellij.psi.*;
|
|
||||||
import com.intellij.psi.tree.IElementType;
|
|
||||||
import com.intellij.ui.DocumentAdapter;
|
|
||||||
import com.intellij.util.IncorrectOperationException;
|
|
||||||
import org.jetbrains.annotations.NonNls;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.event.DocumentEvent;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
import static com.siyeh.ig.psiutils.ExpressionUtils.isNullLiteral;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements an inspection to detect when object references are compared using 'a==b' or 'a!=b'.
|
|
||||||
* The quick fix converts these comparisons to 'a.equals(b) or '!a.equals(b)' respectively.
|
|
||||||
*/
|
|
||||||
public class ComparingReferencesInspection extends AbstractBaseJavaLocalInspectionTool {
|
|
||||||
|
|
||||||
// Defines the text of the quick fix intention
|
|
||||||
public static final String QUICK_FIX_NAME = "SDK: " +
|
|
||||||
InspectionsBundle.message("inspection.comparing.references.use.quickfix");
|
|
||||||
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ComparingReferencesInspection");
|
|
||||||
private final CriQuickFix myQuickFix = new CriQuickFix();
|
|
||||||
// This string holds a list of classes relevant to this inspection.
|
|
||||||
@SuppressWarnings({"WeakerAccess"})
|
|
||||||
@NonNls
|
|
||||||
public String CHECKED_CLASSES = "java.lang.String;java.util.Date";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called to get the panel describing the inspection.
|
|
||||||
* It is called every time the user selects the inspection in preferences.
|
|
||||||
* The user has the option to edit the list of {@link #CHECKED_CLASSES}.
|
|
||||||
*
|
|
||||||
* @return panel to display inspection information.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public JComponent createOptionsPanel() {
|
|
||||||
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
|
||||||
final JTextField checkedClasses = new JTextField(CHECKED_CLASSES);
|
|
||||||
checkedClasses.getDocument().addDocumentListener(new DocumentAdapter() {
|
|
||||||
public void textChanged(@NotNull DocumentEvent event) {
|
|
||||||
CHECKED_CLASSES = checkedClasses.getText();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
panel.add(checkedClasses);
|
|
||||||
return panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is overridden to provide a custom visitor.
|
|
||||||
* that inspects expressions with relational operators '==' and '!='.
|
|
||||||
* The visitor must not be recursive and must be thread-safe.
|
|
||||||
*
|
|
||||||
* @param holder object for visitor to register problems found.
|
|
||||||
* @param isOnTheFly true if inspection was run in non-batch mode
|
|
||||||
* @return non-null visitor for this inspection.
|
|
||||||
* @see JavaElementVisitor
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
|
|
||||||
return new JavaElementVisitor() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This string defines the short message shown to a user signaling the inspection found a problem.
|
|
||||||
* It reuses a string from the inspections bundle.
|
|
||||||
*/
|
|
||||||
@NonNls
|
|
||||||
private final String DESCRIPTION_TEMPLATE = "SDK " +
|
|
||||||
InspectionsBundle.message("inspection.comparing.references.problem.descriptor");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Avoid defining visitors for both Reference and Binary expressions.
|
|
||||||
*
|
|
||||||
* @param psiReferenceExpression The expression to be evaluated.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void visitReferenceExpression(PsiReferenceExpression psiReferenceExpression) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Evaluate binary psi expressions to see if they contain relational operators '==' and '!=', AND they contain
|
|
||||||
* classes contained in CHECKED_CLASSES. The evaluation ignores expressions comparing an object to null.
|
|
||||||
* IF this criteria is met, add the expression to the problems list.
|
|
||||||
*
|
|
||||||
* @param expression The binary expression to be evaluated.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void visitBinaryExpression(PsiBinaryExpression expression) {
|
|
||||||
super.visitBinaryExpression(expression);
|
|
||||||
IElementType opSign = expression.getOperationTokenType();
|
|
||||||
if (opSign == JavaTokenType.EQEQ || opSign == JavaTokenType.NE) {
|
|
||||||
// The binary expression is the correct type for this inspection
|
|
||||||
PsiExpression lOperand = expression.getLOperand();
|
|
||||||
PsiExpression rOperand = expression.getROperand();
|
|
||||||
if (rOperand == null || isNullLiteral(lOperand) || isNullLiteral(rOperand)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Nothing is compared to null, now check the types being compared
|
|
||||||
PsiType lType = lOperand.getType();
|
|
||||||
PsiType rType = rOperand.getType();
|
|
||||||
if (isCheckedType(lType) || isCheckedType(rType)) {
|
|
||||||
// Identified an expression with potential problems, add to list with fix object.
|
|
||||||
holder.registerProblem(expression,
|
|
||||||
DESCRIPTION_TEMPLATE, myQuickFix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies the input is the correct {@code PsiType} for this inspection.
|
|
||||||
*
|
|
||||||
* @param type The {@code PsiType} to be examined for a match
|
|
||||||
* @return {@code true} if input is {@code PsiClassType} and matches one of the classes
|
|
||||||
* in the {@link ComparingReferencesInspection#CHECKED_CLASSES} list.
|
|
||||||
*/
|
|
||||||
private boolean isCheckedType(PsiType type) {
|
|
||||||
if (!(type instanceof PsiClassType)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
StringTokenizer tokenizer = new StringTokenizer(CHECKED_CLASSES, ";");
|
|
||||||
while (tokenizer.hasMoreTokens()) {
|
|
||||||
String className = tokenizer.nextToken();
|
|
||||||
if (type.equalsToText(className)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class provides a solution to inspection problem expressions by manipulating the PSI tree to use 'a.equals(b)'
|
|
||||||
* instead of '==' or '!='.
|
|
||||||
*/
|
|
||||||
private static class CriQuickFix implements LocalQuickFix {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a partially localized string for the quick fix intention.
|
|
||||||
* Used by the test code for this plugin.
|
|
||||||
*
|
|
||||||
* @return Quick fix short name.
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return QUICK_FIX_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method manipulates the PSI tree to replace 'a==b' with 'a.equals(b)' or 'a!=b' with '!a.equals(b)'.
|
|
||||||
*
|
|
||||||
* @param project The project that contains the file being edited.
|
|
||||||
* @param descriptor A problem found by this inspection.
|
|
||||||
*/
|
|
||||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
|
||||||
try {
|
|
||||||
PsiBinaryExpression binaryExpression = (PsiBinaryExpression) descriptor.getPsiElement();
|
|
||||||
IElementType opSign = binaryExpression.getOperationTokenType();
|
|
||||||
PsiExpression lExpr = binaryExpression.getLOperand();
|
|
||||||
PsiExpression rExpr = binaryExpression.getROperand();
|
|
||||||
if (rExpr == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
|
|
||||||
PsiMethodCallExpression equalsCall =
|
|
||||||
(PsiMethodCallExpression) factory.createExpressionFromText("a.equals(b)", null);
|
|
||||||
|
|
||||||
equalsCall.getMethodExpression().getQualifierExpression().replace(lExpr);
|
|
||||||
equalsCall.getArgumentList().getExpressions()[0].replace(rExpr);
|
|
||||||
|
|
||||||
PsiExpression result = (PsiExpression) binaryExpression.replace(equalsCall);
|
|
||||||
|
|
||||||
if (opSign == JavaTokenType.NE) {
|
|
||||||
PsiPrefixExpression negation = (PsiPrefixExpression) factory.createExpressionFromText("!a", null);
|
|
||||||
negation.getOperand().replace(result);
|
|
||||||
result.replace(negation);
|
|
||||||
}
|
|
||||||
} catch (IncorrectOperationException e) {
|
|
||||||
LOG.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getFamilyName() {
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
<!-- Copyright 2000-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
Reports usages of <code>==</code> and <code>!=</code> when comparing instances of specified types.
|
|
||||||
<p>
|
|
||||||
Quick-fix replaces operator with <code>equals()</code> call.
|
|
||||||
</p>
|
|
||||||
<!-- tooltip end -->
|
|
||||||
<p>Configure the inspection:</p>
|
|
||||||
List classes to be inspected separated by semi-colon.
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,6 +0,0 @@
|
|||||||
public class Eq {
|
|
||||||
public boolean compare2Strings(java.lang.String s1, java.lang.String s2) {
|
|
||||||
return (s1.equals(s2));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
public class Eq {
|
|
||||||
public boolean compare2Strings(java.lang.String s1, java.lang.String s2) {
|
|
||||||
return (<caret>s1 == s2);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
public class Neq {
|
|
||||||
public boolean compare2Dates(java.util.Date dt1, java.util.Date dt2){
|
|
||||||
return (!dt1.equals(dt2));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
public class Neq {
|
|
||||||
public boolean compare2Dates(java.util.Date dt1, java.util.Date dt2){
|
|
||||||
return (dt1 <caret>!= dt2);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -5,14 +5,14 @@
|
|||||||
|
|
||||||
Comparing References Inspection Sample demonstrates the implementation of the [Code Inspections][docs:code_inspections] feature for Java classes.
|
Comparing References Inspection Sample demonstrates the implementation of the [Code Inspections][docs:code_inspections] feature for Java classes.
|
||||||
|
|
||||||
The plugin inspects your Java code and highlights any fragments containing the comparison of two `String` or `Date` variables.
|
The plugin inspects your Java code and highlights any fragments containing the comparison of two `String` variables.
|
||||||
If such a check finds a comparison using the `==` or !`=` operators instead of the `.equals()` method, the plugin proposes a *quick-fix* action.
|
If such a check finds a comparison using the `==` or !`=` operators instead of the `.equals()` method, the plugin proposes a *quick-fix* action.
|
||||||
|
|
||||||
### Extension Points
|
### Extension Points
|
||||||
|
|
||||||
| Name | Implementation | Extension Point Class |
|
| Name | Implementation | Extension Point Class |
|
||||||
|--------------------------------|---------------------------------------------------------------------|---------------------------------------|
|
|--------------------------------|---------------------------------------------------------------------------------|---------------------------------------|
|
||||||
| `com.intellij.localInspection` | [ComparingReferencesInspection][file:ComparingReferencesInspection] | `AbstractBaseJavaLocalInspectionTool` |
|
| `com.intellij.localInspection` | [ComparingStringReferencesInspection][file:ComparingStringReferencesInspection] | `AbstractBaseJavaLocalInspectionTool` |
|
||||||
|
|
||||||
*Reference: [Plugin Extension Points in IntelliJ SDK Docs][docs:ep]*
|
*Reference: [Plugin Extension Points in IntelliJ SDK Docs][docs:ep]*
|
||||||
|
|
||||||
@ -21,4 +21,4 @@ If such a check finds a comparison using the `==` or !`=` operators instead of t
|
|||||||
[docs:code_inspections]: https://plugins.jetbrains.com/docs/intellij/code-inspections.html
|
[docs:code_inspections]: https://plugins.jetbrains.com/docs/intellij/code-inspections.html
|
||||||
[docs:ep]: https://plugins.jetbrains.com/docs/intellij/plugin-extensions.html
|
[docs:ep]: https://plugins.jetbrains.com/docs/intellij/plugin-extensions.html
|
||||||
|
|
||||||
[file:ComparingReferencesInspection]: ./src/main/java/org/intellij/sdk/codeInspection/ComparingReferencesInspection.java
|
[file:ComparingStringReferencesInspection]: ./src/main/java/org/intellij/sdk/codeInspection/ComparingStringReferencesInspection.java
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("java")
|
id("java")
|
||||||
@ -36,10 +36,4 @@ tasks {
|
|||||||
sinceBuild.set("221")
|
sinceBuild.set("221")
|
||||||
untilBuild.set("223.*")
|
untilBuild.set("223.*")
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
|
||||||
// Set idea.home.path to the absolute path to the intellij-community source
|
|
||||||
// on your local machine.
|
|
||||||
systemProperty("idea.home.path", "/Users/jhake/Documents/source/comm")
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,3 +1,3 @@
|
|||||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
|
||||||
rootProject.name = "comparing_references_inspection"
|
rootProject.name = "comparing_string_references_inspection"
|
@ -0,0 +1,139 @@
|
|||||||
|
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
|
||||||
|
package org.intellij.sdk.codeInspection;
|
||||||
|
|
||||||
|
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
|
||||||
|
import com.intellij.codeInspection.LocalQuickFix;
|
||||||
|
import com.intellij.codeInspection.ProblemDescriptor;
|
||||||
|
import com.intellij.codeInspection.ProblemsHolder;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.psi.*;
|
||||||
|
import com.intellij.psi.tree.IElementType;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import static com.siyeh.ig.psiutils.ExpressionUtils.isNullLiteral;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements an inspection to detect when String references are compared using 'a==b' or 'a!=b'.
|
||||||
|
* The quick fix converts these comparisons to 'a.equals(b) or '!a.equals(b)' respectively.
|
||||||
|
*/
|
||||||
|
public class ComparingStringReferencesInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||||
|
|
||||||
|
private final ReplaceWithEqualsQuickFix myQuickFix = new ReplaceWithEqualsQuickFix();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is overridden to provide a custom visitor
|
||||||
|
* that inspects expressions with relational operators '==' and '!='.
|
||||||
|
* The visitor must not be recursive and must be thread-safe.
|
||||||
|
*
|
||||||
|
* @param holder object for the visitor to register problems found
|
||||||
|
* @param isOnTheFly true if inspection was run in non-batch mode
|
||||||
|
* @return non-null visitor for this inspection
|
||||||
|
* @see JavaElementVisitor
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
|
||||||
|
return new JavaElementVisitor() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate binary psi expressions to see if they contain relational operators '==' and '!=',
|
||||||
|
* AND they are of String type.
|
||||||
|
* The evaluation ignores expressions comparing an object to null.
|
||||||
|
* IF these criteria are met, register the problem in the ProblemsHolder.
|
||||||
|
*
|
||||||
|
* @param expression The binary expression to be evaluated.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void visitBinaryExpression(PsiBinaryExpression expression) {
|
||||||
|
super.visitBinaryExpression(expression);
|
||||||
|
IElementType opSign = expression.getOperationTokenType();
|
||||||
|
if (opSign == JavaTokenType.EQEQ || opSign == JavaTokenType.NE) {
|
||||||
|
// The binary expression is the correct type for this inspection
|
||||||
|
PsiExpression lOperand = expression.getLOperand();
|
||||||
|
PsiExpression rOperand = expression.getROperand();
|
||||||
|
if (rOperand == null || isNullLiteral(lOperand) || isNullLiteral(rOperand)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Nothing is compared to null, now check the types being compared
|
||||||
|
if (isStringType(lOperand) || isStringType(rOperand)) {
|
||||||
|
// Identified an expression with potential problems, register problem with the quick fix object
|
||||||
|
holder.registerProblem(expression,
|
||||||
|
InspectionBundle.message("inspection.comparing.string.references.problem.descriptor"),
|
||||||
|
myQuickFix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isStringType(PsiExpression operand) {
|
||||||
|
PsiType type = operand.getType();
|
||||||
|
if (!(type instanceof PsiClassType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PsiClass resolvedType = ((PsiClassType) type).resolve();
|
||||||
|
if (resolvedType == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return "java.lang.String".equals(resolvedType.getQualifiedName());
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides a solution to inspection problem expressions by manipulating the PSI tree to use 'a.equals(b)'
|
||||||
|
* instead of '==' or '!='.
|
||||||
|
*/
|
||||||
|
private static class ReplaceWithEqualsQuickFix implements LocalQuickFix {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a partially localized string for the quick fix intention.
|
||||||
|
* Used by the test code for this plugin.
|
||||||
|
*
|
||||||
|
* @return Quick fix short name.
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return InspectionBundle.message("inspection.comparing.string.references.use.quickfix");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method manipulates the PSI tree to replace 'a==b' with 'a.equals(b)' or 'a!=b' with '!a.equals(b)'.
|
||||||
|
*
|
||||||
|
* @param project The project that contains the file being edited.
|
||||||
|
* @param descriptor A problem found by this inspection.
|
||||||
|
*/
|
||||||
|
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||||
|
PsiBinaryExpression binaryExpression = (PsiBinaryExpression) descriptor.getPsiElement();
|
||||||
|
IElementType opSign = binaryExpression.getOperationTokenType();
|
||||||
|
PsiExpression lExpr = binaryExpression.getLOperand();
|
||||||
|
PsiExpression rExpr = binaryExpression.getROperand();
|
||||||
|
if (rExpr == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
|
||||||
|
PsiMethodCallExpression equalsCall =
|
||||||
|
(PsiMethodCallExpression) factory.createExpressionFromText("a.equals(b)", null);
|
||||||
|
|
||||||
|
equalsCall.getMethodExpression().getQualifierExpression().replace(lExpr);
|
||||||
|
equalsCall.getArgumentList().getExpressions()[0].replace(rExpr);
|
||||||
|
|
||||||
|
PsiExpression result = (PsiExpression) binaryExpression.replace(equalsCall);
|
||||||
|
|
||||||
|
if (opSign == JavaTokenType.NE) {
|
||||||
|
PsiPrefixExpression negation = (PsiPrefixExpression) factory.createExpressionFromText("!a", null);
|
||||||
|
negation.getOperand().replace(result);
|
||||||
|
result.replace(negation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getFamilyName() {
|
||||||
|
return getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
|
||||||
|
package org.intellij.sdk.codeInspection;
|
||||||
|
|
||||||
|
import com.intellij.DynamicBundle;
|
||||||
|
import org.jetbrains.annotations.Nls;
|
||||||
|
import org.jetbrains.annotations.NonNls;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.PropertyKey;
|
||||||
|
|
||||||
|
public final class InspectionBundle extends DynamicBundle {
|
||||||
|
|
||||||
|
private static final InspectionBundle ourInstance = new InspectionBundle();
|
||||||
|
|
||||||
|
@NonNls
|
||||||
|
public static final String BUNDLE = "messages.InspectionBundle";
|
||||||
|
|
||||||
|
private InspectionBundle() {
|
||||||
|
super(BUNDLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key,
|
||||||
|
Object @NotNull ... params) {
|
||||||
|
return ourInstance.getMessage(key, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<!-- Copyright 2000-2022 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
|
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||||
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
|
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
|
||||||
|
|
||||||
<idea-plugin>
|
<idea-plugin>
|
||||||
@ -35,37 +35,37 @@
|
|||||||
|
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
<extensions defaultExtensionNs="com.intellij">
|
||||||
<!--
|
<!--
|
||||||
Extend the IntelliJ Platform local inspection type, and connect it to the implementation class in this plugin.
|
Extend the IntelliJ Platform local inspection type and connect it to the implementation class in this plugin.
|
||||||
<localInspection> type element is applied within the scope of a file under edit.
|
<localInspection> type element is applied within the scope of a file under edit.
|
||||||
It is preferred over <inspectionToolProvider>
|
It is preferred over <inspectionToolProvider>
|
||||||
@see intellij.platform.resources.LangExtensionPoints
|
@see intellij.platform.resources.LangExtensionPoints
|
||||||
@see com.intellij.codeInspection.InspectionProfileEntry
|
@see com.intellij.codeInspection.InspectionProfileEntry
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
language= Language ID
|
- language - inspection language ID
|
||||||
shortName= Not specified, will be computed by the underlying implementation classes.
|
- shortName - not specified, will be computed by the underlying implementation classes
|
||||||
displayName= The string to be shown in the Preferences | Editor | Inspections panel
|
- bundle - name of the message bundle for the "key" attribute
|
||||||
The displayName gets registered to identify this inspection.
|
- key - the key of the message to be shown in the Preferences | Editor | Inspections panel
|
||||||
Can be localized using key= and bundle= attributes instead.
|
- groupPath - defines the outermost grouping for this inspection in
|
||||||
groupPath= Defines the outermost grouping for this inspection in
|
|
||||||
the Preferences | Editor | Inspections panel. Not localized.
|
the Preferences | Editor | Inspections panel. Not localized.
|
||||||
groupBundle= Name of *.bundle file to translate groupKey.
|
- groupBundle - the name of a message bundle file to translate groupKey
|
||||||
In this case reuse an IntelliJ Platform bundle file from intellij.platform.resources.en
|
In this case, reuse an IntelliJ Platform bundle file from intellij.platform.resources.en
|
||||||
groupKey= Key to use for translation subgroup name using groupBundle file.
|
- groupKey - the key to use for translation subgroup name using groupBundle file.
|
||||||
In this case reuse the IntelliJ Platform subcategory "Probable bugs"
|
In this case, reuse the IntelliJ Platform subcategory "Probable bugs"
|
||||||
enabledByDefault= Inspection state when Inspections panel is created.
|
- enabledByDefault - inspection state when the Inspections panel is created.
|
||||||
level= The default level of error found by this inspection, e.g. INFO, ERROR, etc.
|
- level - the default level of error found by this inspection, e.g. INFO, ERROR, etc.
|
||||||
@see com.intellij.codeHighlighting.HighlightDisplayLevel
|
@see com.intellij.codeHighlighting.HighlightDisplayLevel
|
||||||
implementationClass= FQN of inspection implementation
|
- implementationClass= the fully-qualified name of the inspection implementation class
|
||||||
-->
|
-->
|
||||||
<localInspection language="JAVA"
|
<localInspection language="JAVA"
|
||||||
displayName="SDK: '==' or '!=' used instead of 'equals()'"
|
bundle="messages.InspectionBundle"
|
||||||
|
key="inspection.comparing.string.references.display.name"
|
||||||
groupPath="Java"
|
groupPath="Java"
|
||||||
groupBundle="messages.InspectionsBundle"
|
groupBundle="messages.InspectionsBundle"
|
||||||
groupKey="group.names.probable.bugs"
|
groupKey="group.names.probable.bugs"
|
||||||
enabledByDefault="true"
|
enabledByDefault="true"
|
||||||
level="WARNING"
|
level="WARNING"
|
||||||
implementationClass="org.intellij.sdk.codeInspection.ComparingReferencesInspection"/>
|
implementationClass="org.intellij.sdk.codeInspection.ComparingStringReferencesInspection"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
</idea-plugin>
|
</idea-plugin>
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
@ -0,0 +1,9 @@
|
|||||||
|
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
Reports usages of <code>==</code> and <code>!=</code> when comparing instances of String.
|
||||||
|
<p>
|
||||||
|
Quick-fix replaces operator with <code>equals()</code> call.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,5 @@
|
|||||||
|
# Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
|
||||||
|
inspection.comparing.string.references.display.name=SDK: '==' or '!=' used instead of 'equals()'
|
||||||
|
inspection.comparing.string.references.problem.descriptor=SDK: String objects compared with equality operation
|
||||||
|
inspection.comparing.string.references.use.quickfix=SDK: Use equals()
|
@ -1,23 +1,31 @@
|
|||||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
|
||||||
package org.intellij.sdk.codeInspection;
|
package org.intellij.sdk.codeInspection;
|
||||||
|
|
||||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
||||||
import com.intellij.codeInsight.intention.IntentionAction;
|
import com.intellij.codeInsight.intention.IntentionAction;
|
||||||
|
import com.intellij.testFramework.TestDataPath;
|
||||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
@TestDataPath("$CONTENT_ROOT/testData")
|
||||||
* Class for testing ComparingReferencesInspection.
|
public class ComparingStringReferencesInspectionTest extends LightJavaCodeInsightFixtureTestCase {
|
||||||
* Requires {@code idea.home.path} to be set in build.gradle.kts.
|
|
||||||
* doTest() does the work for individual test cases.
|
private static final String QUICK_FIX_NAME =
|
||||||
*/
|
InspectionBundle.message("inspection.comparing.string.references.use.quickfix");
|
||||||
public class ComparingReferencesInspectionTest extends LightJavaCodeInsightFixtureTestCase {
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
myFixture.enableInspections(new ComparingStringReferencesInspection());
|
||||||
|
// optimization: add a fake java.lang.String class to avoid loading all JDK classes for test
|
||||||
|
myFixture.addClass("package java.lang; public final class String {}");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines path to files used for running tests.
|
* Defines the path to files used for running tests.
|
||||||
*
|
*
|
||||||
* @return The path from this module's root directory ($MODULE_WORKING_DIR$) to the
|
* @return The path from this module's root directory ($MODULE_WORKING_DIR$) to the
|
||||||
* directory containing files for these tests.
|
* directory containing files for these tests.
|
||||||
@ -27,28 +35,6 @@ public class ComparingReferencesInspectionTest extends LightJavaCodeInsightFixtu
|
|||||||
return "src/test/testData";
|
return "src/test/testData";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Given the name of a test file, runs comparing references inspection quick fix and tests
|
|
||||||
* the results against a reference outcome file. File name pattern 'foo.java' and 'foo.after.java'
|
|
||||||
* are matching before and after files in the testData directory.
|
|
||||||
*
|
|
||||||
* @param testName The name of the test file before comparing references inspection.
|
|
||||||
*/
|
|
||||||
protected void doTest(@NotNull String testName) {
|
|
||||||
// Initialize the test based on the testData file
|
|
||||||
myFixture.configureByFile(testName + ".java");
|
|
||||||
// Initialize the inspection and get a list of highlighted
|
|
||||||
myFixture.enableInspections(new ComparingReferencesInspection());
|
|
||||||
List<HighlightInfo> highlightInfos = myFixture.doHighlighting();
|
|
||||||
assertFalse(highlightInfos.isEmpty());
|
|
||||||
// Get the quick fix action for comparing references inspection and apply it to the file
|
|
||||||
final IntentionAction action = myFixture.findSingleIntention(ComparingReferencesInspection.QUICK_FIX_NAME);
|
|
||||||
assertNotNull(action);
|
|
||||||
myFixture.launchAction(action);
|
|
||||||
// Verify the results
|
|
||||||
myFixture.checkResultByFile(testName + ".after.java");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the '==' case.
|
* Test the '==' case.
|
||||||
*/
|
*/
|
||||||
@ -63,4 +49,26 @@ public class ComparingReferencesInspectionTest extends LightJavaCodeInsightFixtu
|
|||||||
doTest("Neq");
|
doTest("Neq");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the name of a test file, runs comparing references inspection quick fix and tests
|
||||||
|
* the results against a reference outcome file.
|
||||||
|
* File name pattern 'foo.java' and 'foo.after.java' are matching before and after files
|
||||||
|
* in the testData directory.
|
||||||
|
*
|
||||||
|
* @param testName test file name base
|
||||||
|
*/
|
||||||
|
protected void doTest(@NotNull String testName) {
|
||||||
|
// Initialize the test based on the testData file
|
||||||
|
myFixture.configureByFile(testName + ".java");
|
||||||
|
// Initialize the inspection and get a list of highlighted
|
||||||
|
List<HighlightInfo> highlightInfos = myFixture.doHighlighting();
|
||||||
|
assertFalse(highlightInfos.isEmpty());
|
||||||
|
// Get the quick fix action for comparing references inspection and apply it to the file
|
||||||
|
final IntentionAction action = myFixture.findSingleIntention(QUICK_FIX_NAME);
|
||||||
|
assertNotNull(action);
|
||||||
|
myFixture.launchAction(action);
|
||||||
|
// Verify the results
|
||||||
|
myFixture.checkResultByFile(testName + ".after.java");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
public class Eq {
|
||||||
|
public boolean compareStrings(String s1, String s2) {
|
||||||
|
return (s1.equals(s2));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
public class Eq {
|
||||||
|
public boolean compareStrings(String s1, String s2) {
|
||||||
|
return (<caret>s1 == s2);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
public class Neq {
|
||||||
|
public boolean compareStrings(String s1, String s2) {
|
||||||
|
return (!s1.equals(s2));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
public class Neq {
|
||||||
|
public boolean compareStrings(String s1, String s2) {
|
||||||
|
return (s1 <caret>!= s2);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user