-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce More{ASTHelpers,JUnitMatchers,Matchers}
utility classes
#335
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few comments and questions. Let me know what you think, we can discuss this as well Monday :).
Should we link to the class they are actually "extending"? In the Javadoc I mean. For example, should MoreJUnitMatchers
in a way link to JUnitMatchers
?
Sorry for maybe being a bit nitpicky on some of the things, but these classes can potentially be used a lot in the future 👀.
.collect(toImmutableList()); | ||
} | ||
|
||
static boolean isMethodInEnclosingClass(String methodName, VisitorState state) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say it makes sense to just make this public right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way, it would be nice to add a Javadoc here :).
|
||
/** A set of JUnit-specific helpers for working with the AST. */ | ||
public final class MoreJUnitMatchers { | ||
/** Matches JUnit test methods. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should be more specific and call it JUnit Jupiter
, WDYT? The things in here are quite JUnit 5 specific
import java.util.Optional; | ||
import javax.lang.model.type.TypeKind; | ||
|
||
/** A set of JUnit-specific helpers for working with the AST. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for reference, the Javadoc from JUnitMatchers
itself:
- Matchers for code patterns which appear to be JUnit-based tests.
I think mentioning AST
here isn't as accurate as we can be 😬.
/** A set of JUnit-specific helpers for working with the AST. */ | |
/** A set of JUnit Jupiter-specific helper methods and {@link Matcher Matchers}. */ |
* | ||
* @param enclosingClass The class to search in. | ||
* @param methodName The method name to search for. | ||
* @return The method trees of the methods with the given name in the class. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @return The method trees of the methods with the given name in the class. | |
* @return The {@link MethodTree}s of the methods with the given name found in the class. |
Maybe even "in the enclosing class"? Or "given class".
isType("org.junit.jupiter.api.BeforeEach"))); | ||
|
||
/** | ||
* Matches methods which have a {@link org.junit.jupiter.params.provider.MethodSource} annotation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Matches methods which have a {@link org.junit.jupiter.params.provider.MethodSource} annotation. | |
* Matches methods that have a {@link org.junit.jupiter.params.provider.MethodSource} annotation. |
Right?
I think that could be helpful. 👍 I have pushed a commit addressing this as well as implementing your other suggestions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a commit. It looks good to me! 🚀
Suggested commit message:
Introduce `More{ASTHelpers,JUnitMatchers,Matchers}` utility classes (#335)
* | ||
* @param methodSourceAnnotation The {@link org.junit.jupiter.params.provider.MethodSource} | ||
* annotation to extract a method name from. | ||
* @return The name of the factory methods referred to in the annotation if there is only one, or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/methods/method
singular is important here :). Reflowed the sentence a bit, not really happy with the word "Alternatively" that I pushed. Feel free to tweak.
import javax.lang.model.type.TypeKind; | ||
|
||
/** | ||
* A set of JUnit Jupiter-specific helper methods and {@link Matcher Matchers}, adding on to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first sentence is usually the summary in the Javadoc and we try to keep that as concise as possible. I feel the second part "deserves" it's own sentence.
import com.sun.source.tree.Tree; | ||
import com.sun.tools.javac.code.Symbol; | ||
|
||
/** A collection of methods to enhance the use of {@link Matcher}s. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we are missing a link to Matchers
(not to mix up with Matcher
s that is used in the line above 😅).
Will add it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-requested review because I think we should also update the usages where we can. Will add a commit.
Added a commit with some other changes. LMKWYT , happy to discuss :).
Oh w.r.t. the state.findEnclosing(ClassTree.class).getMembers()
, we could add a checkState
there if we want to 👀.
* A set of JUnit Jupiter-specific helper methods and {@link Matcher Matchers}, adding on to the | ||
* ones from {@link com.google.errorprone.matchers.JUnitMatchers}. | ||
*/ | ||
public final class MoreJUnitJupiterMatchers { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit on the fence about the naming.
Although within the class it's a good idea to make explicit it's about JUnit Jupiter things, it makes sense to me to name the class inline with the upstream one, meaning we call it MoreJUnitMatchers
. WDYT?
Additionally, in the near future expect more things will be added not necessarily specific to JUnit Jupiter.
* @param methodName The method name to search for. | ||
* @return Whether there are any methods with the given name in the given class. | ||
*/ | ||
public static boolean isMethodInEnclosingClass(ClassTree enclosingClass, String methodName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed this back to a VisitorState
as that is how it is used in the other place. That also holds for the usage we are introducing in #319. As passing VisitorState
to ASTHelpers
's methods is quite common, I think it does make sense here. WDYT?
Sorry @eric-picnic can you maybe repeat here why this was changed to a ClassTree
:)?
* Matches methods that have a {@link org.junit.jupiter.params.provider.MethodSource} annotation. | ||
*/ | ||
public static final Matcher<MethodTree> HAS_METHOD_SOURCE = | ||
allOf(annotations(AT_LEAST_ONE, isType("org.junit.jupiter.params.provider.MethodSource"))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allOf(annotations(AT_LEAST_ONE, isType("org.junit.jupiter.params.provider.MethodSource"))); | |
annotations(AT_LEAST_ONE, isType("org.junit.jupiter.params.provider.MethodSource")); |
Wouldn't this be sufficient?
54e7619
to
a1c3556
Compare
Latest changes LGTM! Will definitely make the other review easier 🚀 ! The PR is ready for your 👁️ 👁️ @Stephan202. |
6443b04
to
ff44df7
Compare
5e2ab41
to
c841481
Compare
Rebased and added a commit. We discussed offline that we also want to have tests for the new utilities. I created the first one, will work on the rest as well ~soon. Haven't written many of these types of tests, so let's discuss what / how I can improve them 😄. |
541df42
to
6bcc3bd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rebased and added a commit. Test review is TBD.
/** | ||
* Extracts the name of the JUnit factory method from a {@link | ||
* org.junit.jupiter.params.provider.MethodSource} annotation. | ||
* | ||
* @param methodSourceAnnotation The {@link org.junit.jupiter.params.provider.MethodSource} | ||
* annotation to extract a method name from. | ||
* @return The name of the factory method referred to in the annotation. Alternatively, {@link | ||
* Optional#empty()} if there is more than one. | ||
*/ | ||
public static Optional<String> extractSingleFactoryMethodName( | ||
AnnotationTree methodSourceAnnotation) { | ||
ExpressionTree attributeExpression = | ||
((AssignmentTree) Iterables.getOnlyElement(methodSourceAnnotation.getArguments())) | ||
.getExpression(); | ||
Type attributeType = ASTHelpers.getType(attributeExpression); | ||
return attributeType.getKind() == TypeKind.ARRAY | ||
? Optional.empty() | ||
: Optional.of(attributeType.stringValue()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I flagged and resolved several issues with this implementation in #188. IMHO we should port the full functionality here:
- Proper support for multiple factory method names.
- Defaulting to the name of the annotated method if no factory method name is explicitly specified.
* to be found. | ||
* @return Whether there are any methods with the given name in the enclosing class. | ||
*/ | ||
public static boolean isMethodInEnclosingClass(String methodName, VisitorState state) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess methodExitsInEnclosingClass
would be a clearer name.
(Even so: I wonder whether this method sound exist as-is: in general we'd also want to match the arguments.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the name. Ah yes that makes sense, but let's do that once we need it.
* @param state A {@link VisitorState} describing the context in which the given {@link Tree} is | ||
* to be found. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 There is no given tree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added another commit. Did not yet review MoreASTHelpersTest
; as discussed offline we should likely have separate test BugChecker
s for each exposed method.
/** | ||
* Matches methods that have a {@link org.junit.jupiter.params.provider.MethodSource} annotation. | ||
*/ | ||
public static final Matcher<MethodTree> HAS_METHOD_SOURCE = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public static final Matcher<MethodTree> HAS_METHOD_SOURCE = | |
public static final MultiMatcher<MethodTree, AnnotationTree> HAS_METHOD_SOURCE = |
|
||
return diagnosticsMessages.isEmpty() | ||
? Description.NO_MATCH | ||
: buildDescription(tree).setMessage(String.join(", ", diagnosticsMessages)).build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of String.join
we should just do .toString()
, so that with BUG: Diagnostic contains
we can verify that the enumeration doesn't contain unexpected additional checks.
/** | ||
* Returns the names of the JUnit value factory methods specified by the given {@link | ||
* org.junit.jupiter.params.provider.MethodSource} annotation. | ||
* | ||
* @param methodSourceAnnotation The annotation from which to extract value factory method names. | ||
* @return One or more value factory names. | ||
*/ | ||
static ImmutableSet<String> getMethodSourceFactoryNames( | ||
AnnotationTree methodSourceAnnotation, MethodTree method) { | ||
ExpressionTree value = AnnotationMatcherUtils.getArgument(methodSourceAnnotation, "value"); | ||
|
||
if (!(value instanceof NewArrayTree)) { | ||
return ImmutableSet.of(ASTHelpers.constValue(value, String.class)); | ||
} | ||
|
||
NewArrayTree arrayTree = (NewArrayTree) value; | ||
return arrayTree.getInitializers().isEmpty() | ||
? ImmutableSet.of(method.getName().toString()) | ||
: arrayTree.getInitializers().stream() | ||
.map(name -> ASTHelpers.constValue(name, String.class)) | ||
.collect(toImmutableSet()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out this logic still isn't correct. Will fix and add tests for all cases.
3494a88
to
36c710d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Splitted the test cases in MoreASTHelpersTest
. The changes LGTM @Stephan202 , nice improvements. Let's get this one in 😄.
Rebased and added a commit.
7f1dd5e
to
0e7276b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rebased and added three commits (over-all diff is small).
Suggested commit message LGTM :)
* <p>This includes annotations inherited from superclasses due to {@link | ||
* java.lang.annotation.Inherited}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This claim should be tested.
Edit: added a test; it fails. The issue is that this method is misnamed: it's about annotations, not meta-annotations. The single use case we have does look for a meta-annotation, but that's only because this method is apply to a tree that itself represents an annotation. Ideally we directly use Matchers#hasAnnotation
, but for some reason that doesn't work (didn't check why).
Edit 2: but arguably this Matcher
should apply only to AnnotationTree
s, in which case @Inherited
does not apply. Reverted test, dropped the comment.
* | ||
* @param <T> The type of tree to match against. | ||
* @param annotationType The binary type name of the annotation (e.g. | ||
* "org.jspecify.nullness.Nullable", or "some.package.OuterClassName$InnerClassName") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JSpecify annotations were recently moved (not yet in the version we're depending on here).
* "org.jspecify.nullness.Nullable", or "some.package.OuterClassName$InnerClassName") | |
* "org.jspecify.annotations.Nullable", or "some.package.OuterClassName$InnerClassName") |
|
||
private static String createDiagnosticsMessage( | ||
BiFunction<String, VisitorState, Object> function, VisitorState state) { | ||
return ImmutableSet.of("foo", "bar", "baz").stream() | ||
.map( | ||
methodName -> | ||
String.join(": ", methodName, String.valueOf(function.apply(methodName, state)))) | ||
.collect(joining(", ")); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although we generally "call down", in case of inner classes calling methods on the outer class, we place the inner classes at the bottom.
.addSourceLines( | ||
"A.java", | ||
"class A {", | ||
" // BUG: Diagnostic contains: foo: 1, bar: 2, baz: 0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can make sure that if any new method name is added, that all tests are updated by adding start/end markers. This also makes createDiagnosticsMessage
simpler; will push a proposal.
Last changes LGTM. Nice to see that adding the tests improved the quality and the validity of the code 👍🏻. |
More{ASTHelpers,JUnitMatchers,Matchers}
utility classes
This PR breaks out common changes from #319, #214, and #188, to allow them to be merged in any order.