generated from JetBrains/intellij-platform-plugin-template
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
MockitoolsPsiUtil.java
373 lines (339 loc) · 17.7 KB
/
MockitoolsPsiUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
//Copyright 2021 Tamás Balog. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.picimako.mockitools;
import static com.picimako.mockitools.MockitoQualifiedNames.AFTER;
import static com.picimako.mockitools.MockitoQualifiedNames.AT_LEAST;
import static com.picimako.mockitools.MockitoQualifiedNames.AT_MOST;
import static com.picimako.mockitools.MockitoQualifiedNames.CALLS;
import static com.picimako.mockitools.MockitoQualifiedNames.EXTRA_INTERFACES;
import static com.picimako.mockitools.MockitoQualifiedNames.GIVEN;
import static com.picimako.mockitools.MockitoQualifiedNames.IGNORE_STUBS;
import static com.picimako.mockitools.MockitoQualifiedNames.MOCK;
import static com.picimako.mockitools.MockitoQualifiedNames.ORG_MOCKITO_ADDITIONAL_MATCHERS;
import static com.picimako.mockitools.MockitoQualifiedNames.ORG_MOCKITO_ARGUMENT_CAPTOR;
import static com.picimako.mockitools.MockitoQualifiedNames.ORG_MOCKITO_ARGUMENT_MATCHERS;
import static com.picimako.mockitools.MockitoQualifiedNames.ORG_MOCKITO_BDDMOCKITO;
import static com.picimako.mockitools.MockitoQualifiedNames.ORG_MOCKITO_MATCHERS;
import static com.picimako.mockitools.MockitoQualifiedNames.ORG_MOCKITO_MOCKITO;
import static com.picimako.mockitools.MockitoQualifiedNames.ORG_MOCKITO_MOCK_SETTINGS;
import static com.picimako.mockitools.MockitoQualifiedNames.ORG_MOCKITO_STUBBING_STUBBER;
import static com.picimako.mockitools.MockitoQualifiedNames.RESET;
import static com.picimako.mockitools.MockitoQualifiedNames.SPY;
import static com.picimako.mockitools.MockitoQualifiedNames.THEN;
import static com.picimako.mockitools.MockitoQualifiedNames.TIMEOUT;
import static com.picimako.mockitools.MockitoQualifiedNames.TIMES;
import static com.picimako.mockitools.MockitoQualifiedNames.VERIFY;
import static com.picimako.mockitools.MockitoQualifiedNames.WHEN;
import static com.picimako.mockitools.PsiMethodUtil.getQualifier;
import static com.siyeh.ig.psiutils.MethodCallUtils.getMethodName;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import org.jetbrains.annotations.Nullable;
/**
* Utilities for working with Mockito PSI.
*/
public final class MockitoolsPsiUtil {
public static final CallMatcher MOCKITO_OCCURRENCE_BASED_VERIFICATION_MODES = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, TIMES, AT_LEAST, AT_MOST).parameterCount(1);
public static final CallMatcher MOCKITO_WITH_SETTINGS = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, "withSettings");
/**
* The original logic and set of non-mockable types can be found in Mockito's
* <ul>
* <li><a href="https://github.com/mockito/mockito/blob/main/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java">InlineDelegateByteBuddyMockMaker#isTypeMockable(Class) method</a>,</li>
* <li><a href="https://github.com/mockito/mockito/blob/main/src/main/java/org/mockito/internal/creation/bytebuddy/InlineBytecodeGenerator.java">InlineBytecodeGenerator#EXCLUDES set</a>.</li>
* </ul>
*/
private static final Set<String> NON_MOCKABLE_TYPES = Set.of(CommonClassNames.JAVA_LANG_CLASS, CommonClassNames.JAVA_LANG_STRING);
public static final CallMatcher.Simple MOCKITO_MOCK = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, MOCK);
private static final CallMatcher MOCKITO_SPY = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, SPY).parameterCount(1);
private static final CallMatcher BDDMOCKITO_GIVEN = CallMatcher.staticCall(ORG_MOCKITO_BDDMOCKITO, GIVEN).parameterCount(1);
private static final CallMatcher BDDMOCKITO_WILL_X =
CallMatcher.staticCall(ORG_MOCKITO_BDDMOCKITO, "will", "willReturn", "willThrow", "willAnswer", "willCallRealMethod");
private static final CallMatcher BDDMOCKITO_THEN = CallMatcher.staticCall(ORG_MOCKITO_BDDMOCKITO, THEN).parameterCount(1);
private static final CallMatcher MOCKITO_WHEN = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, WHEN).parameterCount(1);
private static final CallMatcher MOCKITO_DO_X_WHEN = CallMatcher.instanceCall(ORG_MOCKITO_STUBBING_STUBBER, WHEN);
private static final CallMatcher MOCKITO_DO_X =
CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, "doReturn", "doThrow", "doAnswer", "doCallRealMethod", "doNothing");
private static final CallMatcher MOCKITO_TIMES = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, TIMES).parameterCount(1);
private static final CallMatcher MOCKITO_CALLS = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, CALLS).parameterCount(1);
private static final CallMatcher MOCKITO_AFTER = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, AFTER).parameterCount(1);
private static final CallMatcher MOCKITO_TIMEOUT = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, TIMEOUT).parameterCount(1);
private static final CallMatcher MOCKITO_RESET = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, RESET);
private static final CallMatcher MOCKITO_IGNORE_STUBS = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, IGNORE_STUBS);
private static final CallMatcher MOCKITO_VERIFY = CallMatcher.staticCall(ORG_MOCKITO_MOCKITO, VERIFY);
private static final CallMatcher MOCK_SETTING_EXTRA_INTERFACES = CallMatcher.instanceCall(ORG_MOCKITO_MOCK_SETTINGS, EXTRA_INTERFACES);
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.mock} method call.
*
* @param expression the method call expression
* @return true if the method is a Mockito.mock, false otherwise
*/
public static boolean isMockitoMock(PsiMethodCallExpression expression) {
return MOCKITO_MOCK.matches(expression);
}
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.spy} method call.
*
* @param expression the method call expression
* @return true if the method is a Mockito.spy, false otherwise
*/
public static boolean isMockitoSpy(PsiMethodCallExpression expression) {
return MOCKITO_SPY.matches(expression);
}
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.when} method call.
*
* @param expression the method call expression
* @return true if the method is a Mockito.when, false otherwise
*/
public static boolean isMockitoWhen(PsiMethodCallExpression expression) {
return MOCKITO_WHEN.matches(expression);
}
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.verify} method call.
*
* @param expression the method call expression
* @return true if the method is a Mockito.verify, false otherwise
*/
public static boolean isMockitoVerify(PsiMethodCallExpression expression) {
return MOCKITO_VERIFY.matches(expression);
}
/**
* Gets whether the argument expression is an {@code org.mockito.BDDMockito.given} method call.
*
* @param expression the method call expression
* @return true if the method is a BDDMockito.given, false otherwise
*/
public static boolean isBDDMockitoGiven(PsiMethodCallExpression expression) {
return BDDMOCKITO_GIVEN.matches(expression);
}
/**
* Gets whether the argument expression is an {@code org.mockito.BDDMockito.will*} method call.
*
* @param expression the method call expression
* @return true if the method is a BDDMockito.will*, false otherwise
*/
public static boolean isBDDMockitoWillX(PsiMethodCallExpression expression) {
return BDDMOCKITO_WILL_X.matches(expression);
}
/**
* Gets whether the argument expression is an {@code org.mockito.BDDMockito.then} method call.
*
* @param expression the method call expression
* @return true if the method is a BDDMockito.then, false otherwise
*/
public static boolean isBDDMockitoThen(PsiMethodCallExpression expression) {
return BDDMOCKITO_THEN.matches(expression);
}
/**
* Gets whether the argument expression is a call on a matcher in {@code org.mockito.AdditionalMatchers}.
*
* @param expression the method call expression
* @return true if the method is a call on an AdditionalMatchers matcher, false otherwise
*/
public static boolean isAdditionalMatchers(PsiMethodCallExpression expression) {
return matchesAnyMethodIn(ORG_MOCKITO_ADDITIONAL_MATCHERS, expression);
}
/**
* Gets whether the argument expression is a call on a matcher in {@code org.mockito.Matchers}.
*
* @param expression the method call expression
* @return true if the method is a call on a Matchers matcher, false otherwise
*/
public static boolean isMatchers(PsiMethodCallExpression expression) {
return matchesAnyMethodIn(ORG_MOCKITO_ARGUMENT_MATCHERS, expression)
&& Optional.ofNullable((PsiReferenceExpression) getQualifier(expression))
.map(qualifier -> (PsiClass) qualifier.resolve())
.filter(matchers -> ORG_MOCKITO_MATCHERS.equals(matchers.getQualifiedName()))
.isPresent();
}
private static boolean matchesAnyMethodIn(String methodFqn, PsiMethodCallExpression expression) {
return CallMatcher.staticCall(methodFqn, getMethodName(expression))
.parameterCount(expression.getArgumentList().getExpressionCount()) //matchers can have various numbers of arguments, so lets match with the current call's parameter count
.matches(expression);
}
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.do...().when} method call.
* <p>
* The corresponding {@code when} method is defined in {@code org.mockito.stubbing.Stubber} which is related to the {@code do...()} family stubbing.
*
* @param expression the method call expression
* @return true if the method is a Mockito.do...().when, false otherwise
*/
public static boolean isMockitoDoXWhen(PsiMethodCallExpression expression) {
return MOCKITO_DO_X_WHEN.matches(expression);
}
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.do*} method call.
*
* @param expression the method call expression
* @return true if the method is a Mockito.do*, false otherwise
*/
public static boolean isMockitoDoX(PsiMethodCallExpression expression) {
return MOCKITO_DO_X.matches(expression);
}
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.times} method call.
*
* @param methodCall the method call expression
* @return true if the method is a Mockito.times, false otherwise
*/
public static boolean isTimes(PsiMethodCallExpression methodCall) {
return MOCKITO_TIMES.matches(methodCall);
}
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.calls} method call.
*
* @param methodCall the method call expression
* @return true if the method is a Mockito.calls, false otherwise
*/
public static boolean isCalls(PsiMethodCallExpression methodCall) {
return MOCKITO_CALLS.matches(methodCall);
}
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.after} method call.
*
* @param methodCall the method call expression
* @return true if the method is a Mockito.after, false otherwise
*/
public static boolean isAfter(PsiMethodCallExpression methodCall) {
return MOCKITO_AFTER.matches(methodCall);
}
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.timeout} method call.
*
* @param methodCall the method call expression
* @return true if the method is a Mockito.timeout, false otherwise
*/
public static boolean isTimeout(PsiMethodCallExpression methodCall) {
return MOCKITO_TIMEOUT.matches(methodCall);
}
/**
* Gets whether the argument expression is an {@code org.mockito.MockitoSettings.extraInterfaces} method call.
*
* @param methodCall the method call expression
* @return true if the method is a MockitoSettings.extraInterfaces, false otherwise
*/
public static boolean isExtraInterfaces(PsiMethodCallExpression methodCall) {
return MOCK_SETTING_EXTRA_INTERFACES.matches(methodCall);
}
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.reset} method call.
*
* @param methodCall the method call expression
* @return true if the method is a Mockito.isReset, false otherwise
*/
public static boolean isReset(PsiMethodCallExpression methodCall) {
return MOCKITO_RESET.matches(methodCall);
}
/**
* Gets whether the argument expression is an {@code org.mockito.Mockito.ignoreStubs} method call.
*
* @param methodCall the method call expression
* @return true if the method is a Mockito.ignoreStubs, false otherwise
*/
public static boolean isIgnoreStubs(PsiMethodCallExpression methodCall) {
return MOCKITO_IGNORE_STUBS.matches(methodCall);
}
/**
* Gets whether the type of the argument field is {@code org.mockito.ArgumentCaptor}.
* <p>
* This logic is used instead of inspecting the PsiType of the field because for the comparison we need only
* the ArgumentCaptor type, but we don't want to inspect the generic type.
*
* @param field the field to inspect the type of
* @return true if the field is org.mockito.ArgumentCaptor, false otherwise
*/
public static boolean isOfTypeArgumentCaptor(PsiField field) {
PsiTypeElement typeElement = field.getTypeElement();
return typeElement != null
&& typeElement.getType() instanceof PsiClassReferenceType
&& ORG_MOCKITO_ARGUMENT_CAPTOR.equals(((PsiClassReferenceType) typeElement.getType()).rawType().getCanonicalText());
}
/**
* Finds the first @DoNotMock annotated type in the class hierarchy, and returns it with the optional reason provided.
*
* @param type the type to check the type hierarchy of for the @DoNotMock annotation
* @return the class annotated as @DoNotMock or null if not found, and the reason provided if any if the class is annotated as @DoNotMock
*/
public static Pair<PsiClass, String> getDoNotMockAnnotatedTypeAndReasonInHierarchy(@Nullable PsiType type) {
if (type instanceof PsiClassType) {
PsiClass referencedClass = ((PsiClassType) type).resolve();
if (referencedClass != null) {
var doNotMock = getDoNotMockAnnotationOn(referencedClass);
if (doNotMock.isPresent()) {
return Pair.create(referencedClass, AnnotationUtil.getStringAttributeValue(doNotMock.get(), "reason"));
}
for (PsiClass cls : InheritanceUtil.getSuperClasses(referencedClass)) {
var doNotMockInHierarchy = getDoNotMockAnnotationOn(cls);
if (doNotMockInHierarchy.isPresent()) {
return Pair.create(cls, AnnotationUtil.getStringAttributeValue(doNotMockInHierarchy.get(), "reason"));
}
}
}
}
return Pair.empty();
}
/**
* This is a simplified version {@link #getDoNotMockAnnotatedTypeAndReasonInHierarchy(PsiType)} that returns a boolean whether any of
* the types in the type hierarchy is annotated with @DoNotMock.
*
* @param type the type to check the type hierarchy of for the @DoNotMock annotation
* @since 0.2.0
*/
private static boolean isDoNotMockAnnotatedInHierarchy(@Nullable PsiType type) {
if (type instanceof PsiClassType) {
PsiClass referencedClass = ((PsiClassType) type).resolve();
if (referencedClass != null) {
return getDoNotMockAnnotationOn(referencedClass).isPresent()
|| InheritanceUtil.getSuperClasses(referencedClass).stream().anyMatch(cls -> getDoNotMockAnnotationOn(cls).isPresent());
}
}
return false;
}
private static Optional<PsiAnnotation> getDoNotMockAnnotationOn(PsiClass clazz) {
return !CommonClassNames.JAVA_LANG_OBJECT.equals(clazz.getQualifiedName())
? Arrays.stream(clazz.getAnnotations())
.filter(annotation -> annotation.getQualifiedName().endsWith(MockitoQualifiedNames.ORG_MOCKITO_DO_NOT_MOCK))
.findFirst()
: Optional.empty();
}
/**
* Returns whether the argument type is mockable, be it not restricted by Mockito itself, or by a @DoNotMock annotation.
*
* @since 0.2.0
*/
public static boolean isMockableTypeInAnyWay(@Nullable PsiType type) {
return isMockableType(type) && !isDoNotMockAnnotatedInHierarchy(type);
}
/**
* Gets whether the argument type is mockable by Mockito.
*
* @param type the type to validate
* @return true if the type is mockable, false otherwise
* @see #NON_MOCKABLE_TYPES
*/
public static boolean isMockableType(@Nullable PsiType type) {
return type != null
&& !TypeConversionUtil.isPrimitiveWrapper(type)
&& !TypeConversionUtil.isPrimitive(type.getCanonicalText())
&& !NON_MOCKABLE_TYPES.contains(type.getCanonicalText());
}
private MockitoolsPsiUtil() {
//Utility class
}
}