Skip to content
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 a check to simplify time expressions in annotations #17

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

nathankooij
Copy link
Contributor

I wish to expand the test suite further, but it should already give a good indication of what is possible with this check.

A few things:

  • I am not entirely certain if what I'm doing in getTimeUnit and getDefaultTimeUnit is always safe.
  • We can extract some of the things to utilities which will a) make this code cleaner b) allow us to re-use it in other checks, some things:
    - Returning a map of attributes -> expression (or assignment trees if that's always the tree) by the matcher;
    - Extracting enum value from an expression;
    - Extracting default attributes on annotations;
    - An "annotation builder" to avoid special casing the implict value attribute.

I know @Stephan202 mentioned he has some of these on a branch already, so curious to see what we can already do out of the box. :)

Comment on lines 208 to 212
MethodSymbol argumentSymbol =
(MethodSymbol)
Iterables.getOnlyElement(
scope.getSymbols(symbol -> symbol.getQualifiedName().contentEquals(argument)));
return (VarSymbol) argumentSymbol.getDefaultValue().getValue();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is perhaps dodgy (and NullAway complains about #getDefaultValue here, although the Iterables#getOnlyElement could be just as problematic), but I assumed that the AnnotationDescriptor is correct (and thus the attribute must exist). Maybe we should just return an Optional<VarSymbol> instead, and short circuit in trySimplification if we couldn't find a time unit for whatever reason. WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think returning the Optional with the short circuit is a clean solution and makes NullAway happy :). So sounds good to me.


private static Fix getImplicitValueAttributeFix(
AnnotationTree annotation, Number newValue, String timeUnitField, TimeUnit newTimeUnit) {
@SuppressWarnings("TreeToString")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of annotation#toString below, but SuggestedFixes#updateAnnotationArgumentValues does this too. 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed something that we can do specifically in Error Prone Support because Stephan added a Util.

}

private static Optional<TimeSimplifier.Simplification> trySimplify(Number value, TimeUnit unit) {
checkArgument(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how defensive we want to be here, or if we should prefer to return empty and avoid exceptions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also not sure, no preference here.

Copy link
Member

@rickie rickie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really nice BugPattern 🚀

tags = BugPattern.StandardTags.SIMPLIFICATION)
public final class SimplifyTimeAnnotationCheck extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final AnnotationAttributeMatcher ARGUMENT_SELECTOR = getMatcher();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use a more descriptive name here instead of getMatcher. It is not directly clear what the matcher does.

private static final AnnotationAttributeMatcher ARGUMENT_SELECTOR = getMatcher();

@Override
public Description matchAnnotation(AnnotationTree annotationTree, VisitorState visitorState) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public Description matchAnnotation(AnnotationTree annotationTree, VisitorState visitorState) {
public Description matchAnnotation(AnnotationTree annotationTree, VisitorState state) {

We typically use state instead of visitorState.

* in the given {@code unit}.
*/
public long toUnit(TimeUnit unit) {
return unit.convert(value, this.unit);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the this.unit I got a HidingField violation. Therefore renamed the variable.

private static Optional<TimeSimplifier.Simplification> trySimplify(Number value, TimeUnit unit) {
checkArgument(
value instanceof Integer || value instanceof Long,
"Only time expressed as a long or integer can be simplified");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turned these two around to be in line with the order above this line.


private static Fix getImplicitValueAttributeFix(
AnnotationTree annotation, Number newValue, String timeUnitField, TimeUnit newTimeUnit) {
@SuppressWarnings("TreeToString")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed something that we can do specifically in Error Prone Support because Stephan added a Util.

}

private static Optional<TimeSimplifier.Simplification> trySimplify(Number value, TimeUnit unit) {
checkArgument(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also not sure, no preference here.

Comment on lines 208 to 212
MethodSymbol argumentSymbol =
(MethodSymbol)
Iterables.getOnlyElement(
scope.getSymbols(symbol -> symbol.getQualifiedName().contentEquals(argument)));
return (VarSymbol) argumentSymbol.getDefaultValue().getValue();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think returning the Optional with the short circuit is a clean solution and makes NullAway happy :). So sounds good to me.

.orElse(Description.NO_MATCH);
}

private static Optional<Fix> trySimplification(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trySimplification in itself is not too descriptive. It makes sense because it is in this particular class, but I would prefer something like the name simplifyAttributeValue which is used in the CanonicalAnnotationSyntaxCheck.

Especially because this file has now a method with the name trySimplificiation and 2 methods with trySimplify. I know one is strictly speaking in another class (the TimeSimplifier) but that's why I think changing the naming could make things a little more clear.

I want to remark the same for getValue since it has two arguments which makes it harder to directly see what is going on there. Changing the name could help there.

Edit:
Hmm, I see that SpringMvcAnnotationCheck does have the same setup as here.. So I get where you are coming from. Although I do think it would be an improvement 😄 .

import java.util.stream.Stream;

/**
* A {@link BugChecker} which flags methods annotated with annotations described by {@link
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* A {@link BugChecker} which flags methods annotated with annotations described by {@link
* A {@link BugChecker} which flags methods annotated with annotations described by the {@link

👀

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think adding "the" is better. That said, will propose a more extensive rephrasing 👇

Copy link
Member

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a small commit and did a partial review. More later/another time :)

* order.
*/
private static ImmutableSortedSet<TimeUnit> descendingLargerUnits(TimeUnit unit) {
return ImmutableSortedSet.copyOf(TimeUnit.values())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can extract this one to a constant.

}

/** Utility class to help simplify time expressions. */
private static final class TimeSimplifier {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class name hints more at it being a type with instance methods. I think we can move the static methods to the Simplification type.

Comment on lines 39 to 46
/**
* A {@link BugChecker} which flags methods annotated with annotations described by {@link
* AnnotationDescriptor} that can be written more concisely.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* A {@link BugChecker} which flags methods annotated with annotations described by {@link
* AnnotationDescriptor} that can be written more concisely.
*/
/**
* A {@link BugChecker} which flags annotations with time attributes that can be written more
* concisely.
*/

import java.util.stream.Stream;

/**
* A {@link BugChecker} which flags methods annotated with annotations described by {@link
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think adding "the" is better. That said, will propose a more extensive rephrasing 👇

@nathankooij
Copy link
Contributor Author

Thx for the feedback guys! I'll try to circle back to this PR somewhere this week. :)

@rickie rickie added the WIP Work in progress label Feb 19, 2022
@Stephan202 Stephan202 force-pushed the nkooij/time-unit-annotations branch from 8baa0c9 to 8ced020 Compare November 5, 2022 16:25
Copy link
Member

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebased, resolved conflicts and added a commit. (Won't do a full review now, but saw that I had the branch checked out, and was wondering about its state.)

@@ -43,7 +42,7 @@ void identification() {
" // BUG: Diagnostic contains:",
" @RequestMapping(method = {DELETE}) A delete();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = {PATCH}) A patch();",
" @RequestMapping(method = {org.springframework.web.bind.annotation.RequestMethod.PATCH}) A patch();",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 Unrelated change?

(Not a bad one, just surprising.)

@rickie
Copy link
Member

rickie commented Aug 24, 2023

I encountered the CanonicalDuration BugPattern today. I made me think of this check, maybe there is some logic there that we can (re-)use?

@Stephan202
Copy link
Member

Yep, likely! Too bad it's all private, so that would entail some copying. Alternatively we could try to contribute improvements upstream, possibly by extracting shared logic to a separate class. (For this annotation case I'm not sure that a banlist applies.) Would then first open an issue to discuss that possibility, though.

Thx for the feedback guys! I'll try to circle back to this PR somewhere this week. :)

I suppose that @nathankooij currently resides in the vicinity of a black hole ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
WIP Work in progress
Development

Successfully merging this pull request may close these issues.

3 participants