How to create a new inspecion for IDEA
Tags: kotlin, java, idea, jetbrains
Published: 2022-03-15 07:48:53

_Disclaimer: I don't work for JetBrains, so my code may have some errors and it is only an example._


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7bw1q8fi82j86vghsmsy.png)

#Introduction
Every large company has its code style. And it is necessary to make people use it.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qflb04oxrusnpt0lnzlh.png)

That's why all checks should be automated (ideally, with CI). For me, the most useful tool is your IDE. We are going to write a simple inspection for IDEA (might be the most popular IDE for Java)

Every IDEA is a base part and a set of plugins for it. Almost every feature can be considered as a plugin, so adding a new one is easy;

#Technical specification

One of the most popular questions in a technical interview is about [HashMap ](https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html)and `equals and hashCode` (links for [hashCode](https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode()), [HashMap](http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/a006fa0a9e8f/src/share/classes/java/util/HashMap.java#l143))

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oxsys3c00noi2vbjfzqc.jpg)

So in our company, we have the rule, that every class which we use as a key in HashMap (HashCode) must override `equals `and `hashCode`. Unfortunately, IDEA doesn't contain an inspection for that ([issue](https://youtrack.jetbrains.com/issue/IDEA-38577)), but we can fix it!

We are going to support the next expressions:
* `new HashMap<>();`
* `new HashSet<>();`
* `.collect(Collectors.toSet());`
* `.collect(Collectors.toMap());`

#Where do we begin?

The start points are:
* [Documentation](https://plugins.jetbrains.com/docs/intellij/welcome.html)
* [Source code of IDEA](https://github.com/JetBrains/intellij-community)

Now we have a great [template ](https://github.com/JetBrains/intellij-platform-plugin-template), which contains a useful todo list

> Template ToDo list
> - [x] Create a new [IntelliJ Platform Plugin Template][template] project.
> - [ ] Verify the [pluginGroup](/gradle.properties), [plugin ID](/src/main/resources/META-INF/plugin.xml) and [sources package](/src/main/kotlin).
> - [ ] Review the [Legal Agreements](https://plugins.jetbrains.com/docs/marketplace/legal-agreements.html).
> - [ ] [Publish a plugin manually](https://plugins.jetbrains.com/docs/intellij/publishing-plugin.html?from=IJPluginTemplate) for the first time.
> - [ ] Set the Plugin ID in the above README badges.
> - [ ] Set the [Deployment Token](https://plugins.jetbrains.com/docs/marketplace/plugin-upload.html).
> - [ ] Click the <kbd>Watch</kbd> button on the top of the [IntelliJ Platform Plugin Template][template] to be notified about releases containing new features and fixes.

Also, there are several simple [examples](https://github.com/JetBrains/intellij-sdk-code-samples), which help us to figure out how all is working.

#Implementation
The source code of the plugin is [here](https://github.com/pyltsin/sniffer).

Every plugin for IDEA must contain a file `resources/META-INF/plugin.xml`, which describes used extension points. if we want to create a new inspection, we have to use `localInspection` and implement [LocalInspectionTool](https://upsource.jetbrains.com/idea-ce/file/idea-ce-ba0c8fc9ab9bf23a71a6a963cd84fc89b09b9fc8/platform/analysis-api/src/com/intellij/codeInspection/LocalInspectionTool.java) or `AbstractBaseJavaLocalInspectionTool` for Java)

```xml
<extensions defaultExtensionNs="com.intellij">
  <localInspection language="JAVA"
                   displayName="Sniffer: Using HashMap with default hashcode"
                   groupPath="Java"
                   groupBundle="messages.SnifferInspectionsBundle"
                   groupKey="group.names.sniffer.probable.bugs"
                   enabledByDefault="true"
                   level="WEAK WARNING"
                   implementationClass="com.github.pyltsin.sniffer.EqualsHashCodeOverrideInspection"/>
</extensions>
```

The necessary method is `buildVisitor`

```java
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder,
                                        final boolean isOnTheFly, 
                                        @NotNull LocalInspectionToolSession session)
```
It is an example of [Visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern)

To work with your code, IDEA creates PSI Tree (PSI - Program Structure Interface). You can see this tree, using [PSI Viewer](https://www.jetbrains.com/help/idea/psi-viewer.html) (Tools->View PSI Structure)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6f5gnccy9ddq1baavr1.png)

If you want to read more about PSI you have to use the [documentation](https://plugins.jetbrains.com/docs/intellij/implementing-parser-and-psi.html)

Our visitor will go through all leaves of the tree and decide if it contains something wrong or not. 
Let's start with an easy case:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i3of2716xsags65s7b6g.png)

The leaf `new HashMap<>()` matches `PsiNewExpression `, so we have to override this method:
```kotlin
    override fun buildVisitor(
        holder: ProblemsHolder,
        isOnTheFly: Boolean,
        session: LocalInspectionToolSession
    ): PsiElementVisitor {
        return object : JavaElementVisitor() {
            override fun visitNewExpression(expression: PsiNewExpression?) {
                super.visitNewExpression(expression)
            }
        }

```

IDEA contains a lot of useful utils classes, methods, constants, for example, `PsiReferenceUtil`, `PsiTypesUtil`.

Firstly, I need to check if this leaf implements `HashMap `or `HashSet`

```kotlin
expression.classOrAnonymousClassReference?.qualifiedName 
in (JAVA_UTIL_HASH_MAP, JAVA_UTIL_HASH_SET)
```
`JAVA_UTIL_HASH_MAP`, `JAVA_UTIL_HASH_SET `- String constants from `com.intellij.psi.CommonClassNames`. This class contains almost all important names.

Secondly, I need to get a class of the key. Thank IDEA, it has already been done for us.

```kotlin
val keyType: PsiType = 
expression.classOrAnonymousClassReference?.parameterList?.typeArguments[0]
```

Then I need to check this class if it overrides hashCode and equals method:

```kotlin
private fun hasOverrideHashCode(psiType: PsiType): Boolean {
  // get PsiClass
  val psiClass = PsiTypesUtil.getPsiClass(psiType) 
   // get similar methods by name
  val methods: Array<PsiMethod> =
  psiClass?.findMethodsByName(HardcodedMethodConstants.HASH_CODE, false) ?: arrayOf()
  // check, if it is hashCode
  return methods.any { MethodUtils.isHashCode(it) }
}
```

Finally, I have to register the problem:
```kotlin
holder.registerProblem(
		expression,
		"hashCode is not overriden"
)
```

Now let's consider an example with Stream:

```java
Map<Clazz2, Clazz2> collect1 = Stream.of(new Clazz2(), new Clazz2())
.collect(Collectors.toMap(t -> t, t -> t));
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/id66fx22vxgqpocevudj.png)

In this case we work with `PsiMethodCallExpression`
```kotlin
override fun visitMethodCallExpression(expression: PsiMethodCallExpression?)
```
For the first check CallMatcher is useful:
```kotlin
val matcher = CallMatcher.instanceCall(JAVA_UTIL_STREAM_STREAM, "collect")
val isCollect = matcher.matches(expression)
```

After that, Collectors.toMap() must be checked:

```
val collectorExpression = expression.argumentList.expressions[0]
val isToMap = CallMatcher.staticCall(JAVA_UTIL_STREAM_COLLECTORS, "toMap")
  .matches(collectorExpression)
```
To get class the next method can be applied:

```kotlin
val psiType = expression.methodExpression.type.parameters[0]
```
After that, we can reuse our previous code to check overriden equals and hashCode.

For tests, Idea has `LightJavaInspectionTestCase `class. ([examples](https://github.com/JetBrains/intellij-community))

Finally, our result:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1vo9bcaueq3w2ltz8u3k.png)

Of course, you can simply enhance these ideas for other cases.

It is really important to get to know your tools!