SSR - Check whether an attribute value (immediate class type) is an interface

HI Everyone,

The context

I'm working on a permanent SSR inspection that would check Mockito's @Mock annotation whether the classes in its extraInterfaces attribute are actually interfaces.

So there may be two cases: a single interface or, an array of interfaces specified, that I'm interested in:

@Mock(extraInterfaces = SearchContext.class)
@Mock(extraInterfaces = {SearchContext.class, List.class})

 

The template

My template so far is:

@org.mockito.Mock($extraInterfaces$ = $attributes$)
@Modifier("Instance") $FieldType$ $field$;

with the following Script filter for Complete Match (I know it may be optimized a bit):

boolean hasOnlyInterfaces = true
//A single annotation attribute value like @Mock(extraInterfaces = SearchContext.class)
if (attributes.getType() instanceof com.intellij.psi.impl.source.PsiImmediateClassType) {
if (!attributes.getType().resolve().isInterface()) {
hasOnlyInterfaces = false
}
}
//An array of annotation attribute values like @Mock(extraInterfaces = {SearchContext.class, List.class})
else if (attributes instanceof com.intellij.psi.PsiArrayInitializerMemberValue) {
attributes.getInitializers().each { attribute ->
//This is the problematic part because getType() doesn't exist on this type
if (!attribute.getType().resolve().isInterface()) {
hasOnlyInterfaces = false
}
}
}

!hasOnlyInterfaces

 

With the template above if I have a single attribute value, the $attributes$ node is recognized as a PsiImmediateClassType from which I can easily resolve its PsiClass and call isInterface() on it. But with array values it is different, the node in that case is a PsiArrayInitializerMemberValue from which (or from its PsiAnnotationMemberValue initializers) I haven't been able to find out how I could get any type info, especially a PsiClass to check whether they are interfaces or not.

My questions are:

  • Since PsiClass and PsiArrayInitializerMemberValue seem to be on a different "branch" of classes (one implementing JvmElement, the other implementing PsiElement), is there any way get a PsiClass instance from this PsiElement?
  • Is there any other way of retrieving the type of the annotation attribute values?

 

Thank you in advance!

0
2 comments
Official comment

Your script retrieves the type of the class object access expression, which is always `java.lang.Class`.  Here is a script that should work:

import com.intellij.psi.*;
boolean hasOnlyInterfaces = true
//A single annotation attribute value like @Mock(extraInterfaces = SearchContext.class)
if (attributes instanceof PsiClassObjectAccessExpression) {
PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)attributes.firstChild?.firstChild;
if (!ref?.resolve()?.isInterface()) {
hasOnlyInterfaces = false
}
}
//An array of annotation attribute values like @Mock(extraInterfaces = {SearchContext.class, List.class})
else if (attributes instanceof PsiArrayInitializerMemberValue) {
attributes.getInitializers().each { attribute ->
if (attribute instanceof PsiClassObjectAccessExpression) {
PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)attribute.firstChild?.firstChild;
if (!ref?.resolve()?.isInterface()) {
hasOnlyInterfaces = false
}
}
}
}

!hasOnlyInterfaces

This script could be simplified a bit by splitting the template into two separate ones, one for each case:

@org.mockito.Mock(extraInterfaces = $attributes$)
@Modifier("Instance") $FieldType$ $field$;
@org.mockito.Mock(extraInterfaces = {$attributes$/*[1,∞]*/})
@Modifier("Instance") $FieldType$ $field$;

Then you would only need the 

attributes instanceof PsiClassObjectAccessExpression

case. 

Hope this helps,

Bas

Thank you for the suggestions, I managed to learn some new things from that.

I gave them a try and they seem to be working like a charm.

I realized that for the second template (the one with the curly brackets) the elements will automatically be a List (or a single object obviously) so I don't need bother with checking PsiArrayInitializerMemberValue, just simply iterate through the items. So the seconds script filter could be simplified to this one:

import com.intellij.psi.*;

boolean hasOnlyInterfaces = true

attributes.each { attribute ->
if (attribute instanceof PsiClassObjectAccessExpression) {
PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)attribute.firstChild?.firstChild;
if (!ref?.resolve()?.isInterface()) {
hasOnlyInterfaces = false
}
}
}
!hasOnlyInterfaces

 

I'm not quite sure that this is exactly what you meant because I cannot see how attributes could be instance of PsiClassObjectAccessExpression when the annotation attribute has multiple values. Though it definitely would work for a single element.

0

Please sign in to leave a comment.