0

Motivation: Every class/interface/annotation/enum has to be annotated by @SomeAnnotation. However we want this annotation to be only at the top level classes and not at the inner ones.

The goal is to create a structural inspection which warns developers that they forgot to annotate the classes. How can I specify the structural search/replace to find all such top level structures missing the @SomeAnnotation?

imparente
  • 151
  • 5

1 Answers1

2

Something like this should work:

@$Annotation$ // min: 0, max: 0, text/regexp: SomeAnnotation
class $C$ {} // min: 1, max: 1

// Complete Match - Script text: 
if (C instanceof com.intellij.psi.PsiIdentifier) C = C.parent
C.containingClass == null && !(C instanceof com.intellij.psi.PsiAnonymousClass)

First line of the script is necessary for IntelliJ IDEA 14. The C in the script references $C$ in the pattern.

Replacement template:

@SomeAnnotation
class $C$ {}

Full template for importing (using the Import Template from Clipboard under the tool button in the dialog):

<replaceConfiguration name="Method calls" text="@$Annotation$&#10;class $C$ {}" recursive="false" caseInsensitive="true" type="JAVA" pattern_context="default" reformatAccordingToStyle="false" shortenFQN="false" replacement="@SomeAnnotation&#10;class $C$ {}">
  <constraint name="__context__" script="&quot;if (C instanceof com.intellij.psi.PsiIdentifier) C = C.parent &#10;C.containingClass == null &amp;&amp; !(C instanceof com.intellij.psi.PsiAnonymousClass)&quot;" within="" contains="" />
  <constraint name="Annotation" regexp="SomeAnnotation" minCount="0" maxCount="0" within="" contains="" />
  <constraint name="C" within="" contains="" />
</replaceConfiguration>
Bas Leijdekkers
  • 23,709
  • 4
  • 70
  • 68
  • Good job on figuring that out! On IntelliJ IDEA 15 my script works. Sorry I didn't mention I tested on IntelliJ IDEA 15 (this is a reply on a comment that has been removed). – Bas Leijdekkers Nov 11 '15 at 15:35
  • Thank you. It works, however it works in Idea 15 only. Our company cannot force each developer to use the latest version especially when there are a lot of customizations for the old versions going on. I made it working in Idea 14 as well with slightly different script: `(C.getParent() instanceof com.intellij.psi.PsiClass) && ((com.intellij.psi.PsiClass) C.getParent()).getContainingClass() == null` however that works in exchange only in Idea 14 and not in Idea 15 (don't ask me why). Do you have any idea how to make it work in Idea 14 and 15 at the same time? – imparente Nov 11 '15 at 15:52
  • I modified to solution to work in both versions of IntelliJ IDEA. – Bas Leijdekkers Nov 11 '15 at 16:10
  • Thank you Bas. It works. Could you please advise where I can find any information about what I can use in Idea 15? If I display the PSI Structure of any class it always shows me that the `$C$` corresponding part is a `PsiIdentifier` so I don't understand why it is not a `PsiIdentifier` in the search script in Idea 15. – imparente Nov 12 '15 at 15:02
  • In IntelliJ IDEA 15 this was changed to return the psi element of the structure that was found. I.e. the PsiVariable, PsiMethod, PsiClass etc. Basically this always corresponds to the parent of the PsiIdentifier in IntelliJ IDEA 14. Two reasons for this are: 1. the Edit Variable options also apply to these elements. 2. A PsiIdentifier is largely useless, and almost always it's parent is needed. – Bas Leijdekkers Nov 12 '15 at 15:14
  • Got it. BTW is there any way how to make a replacement template which would add the `@SomeAnnotation` and wouldn't remove all other possible existing class annotations including their parameters? – imparente Nov 12 '15 at 17:15
  • This is great but it also seems to be matching on anonymous class instances such as `var foo = new TypeLiteral() {};`. Is there any way this could be extended to not match these? – Jeremy Jan 14 '21 at 16:41
  • I have expanded my answer to exclude anonymous classes and added an example replacement template. – Bas Leijdekkers Jan 15 '21 at 11:00