-
Notifications
You must be signed in to change notification settings - Fork 27
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
feat: Add processor fixing sonar rule S4065_ThreadLocalWithInitial #706
feat: Add processor fixing sonar rule S4065_ThreadLocalWithInitial #706
Conversation
if (statement instanceof CtReturn) { | ||
lambda.setExpression(getReturnStatement(statement)); | ||
} else { | ||
lambda.setBody(initalValueMethod.getDeclaration().getBody()); |
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 I'm unsure if there is a case where the method initalValue
has one statement, and it is not a CtReturn
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 don't think it is possible. I think a Supplier
needs to return something. At the least, return null
should be there.
This rule does not show up on https://rules.sonarsource.com/ so I thought it was deprecated. However, their JIRA dashboard shows that the rule is still active. |
https://rules.sonarsource.com/java/RSPEC-4065 here is the link |
I was prepending 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.
Thanks for the PR, @MartinWitt !
I have posted some general thoughts about this PR as comments. Please have a look.
if (isThreadLocalType(newClass)) { | ||
CtClass<?> innerClass = newClass.getAnonymousClass(); | ||
if (hasNoFields(innerClass) && hasOnlyConstructorAndSingleMethod(innerClass)) { | ||
Optional<CtExecutableReference<?>> initalValueMethod = | ||
findInitalValueMethod(innerClass); | ||
if (initalValueMethod.isPresent()) { |
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 think you can remove the if-condition
s you have here. It is not necessary to check that the type of newClass
is ThreadLocal
because if it is not, SonarJava would never report a violation and we don't even need to visit this processor in that case.
Following the same logic as above, initialValueMethod
must be present for the violation to be reported.
I am not sure what is the behaviour of SonarJava when there are more than one type members. If they report a violation, they have not illustrated how to fix it. So I am unsure if we need to invoke hasNoFields
and hasOnlyConstructorAndSingleMethod
.
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 would leave some defensive programming here because it does not harm.
if (statement instanceof CtReturn) { | ||
lambda.setExpression(getReturnStatement(statement)); | ||
} else { | ||
lambda.setBody(initalValueMethod.getDeclaration().getBody()); |
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 don't think it is possible. I think a Supplier
needs to return something. At the least, return null
should be there.
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.
These are my final suggestions. I have commented on defensive programming and why we may opt-out here. Also, have a look at the refactoring comments.
CtLambda<?> lambda = getFactory().createLambda(); | ||
if (initalValueMethod.getDeclaration().getBody().getStatements().size() == 1) { | ||
CtStatement statement = initalValueMethod.getDeclaration().getBody().getStatement(0); | ||
if (statement instanceof CtReturn) { |
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.
We don't need this check. Supplier
will have a return statement.
if (hasNoFields(innerClass) && hasOnlyConstructorAndSingleMethod(innerClass)) { | ||
Optional<CtExecutableReference<?>> initalValueMethod = | ||
findInitalValueMethod(innerClass); | ||
if (initalValueMethod.isPresent()) { |
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 think we can also remove the .isPresent
check because if it would not be present, then it would not be a violation.
Defensive programming is good only when we are unsure about the consequences. For example, we don't know what is supposed to happen when the anonymous class has more than 2 type members. So it is better to add a check for them as you have done above. Moreover, defensive programming reduces coverage as well so we should use it when we really need it.
} | ||
} | ||
|
||
private CtInvocation<?> createInitalMethod(CtNewClass<?> threadLocal, CtLambda<?> lambda) { |
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.
private CtInvocation<?> createInitalMethod(CtNewClass<?> threadLocal, CtLambda<?> lambda) { | |
private CtInvocation<?> createInitialMethod(CtNewClass<?> threadLocal, CtLambda<?> lambda) { |
There is a typographical error in the spelling of initial
. This is just one instance. Please correct them everywhere.
.createInvocation( | ||
getFactory().createTypeAccess(createThreadLocalRef()), | ||
getFactory() | ||
.Executable() | ||
.createReference( | ||
threadLocal.getType(), | ||
true, | ||
threadLocal.getType(), | ||
"withInitial", | ||
List.of(lambda.getType()))); |
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.
.createInvocation( | |
getFactory().createTypeAccess(createThreadLocalRef()), | |
getFactory() | |
.Executable() | |
.createReference( | |
threadLocal.getType(), | |
true, | |
threadLocal.getType(), | |
"withInitial", | |
List.of(lambda.getType()))); | |
CtTypeAccess<?> target = getFactory().createTypeAccess(createThreadLocalRef()); | |
CtExecutableReference<?> executableReference = getFactory() | |
.Executable() | |
.createReference(threadLocal.getType(), true, threadLocal.getType(), "withInitial", List.of(lambda.getType())); | |
return getFactory().createInvocation(target, executableReference); |
Probably a refactoring like this is more readable? You could also create the reference to withInitial
in a separate method.
if (initalValueMethod.getDeclaration().getBody().getStatements().size() == 1) { | ||
CtStatement statement = initalValueMethod.getDeclaration().getBody().getStatement(0); | ||
if (statement instanceof CtReturn) { | ||
lambda.setExpression(getReturnStatement(statement)); |
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.
lambda.setExpression(getReturnStatement(statement)); | |
lambda.setExpression(((CtReturn<T>) statement).getReturnedExpression()); |
In my opinion, this is better as is. No need to create another method for it, but it is up to you.
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.
We need this method for the type system, otherwise we would need to use raw types. Raw type is worse than an unchecked cast for me.
Thanks @MartinWitt a lot. Per discussion with @algomaster99, we may not be able to fix some of the violations reported by Sonar. For example, if the anonymous class contains more than one (+ an implicit constructor) type members. To make sure, we have to see if such violations are reported by Sonar. If they are, we can make this an incomplete processor. |
Thanks @MartinWitt . @algomaster99 already tested it, it reports no violation. We're good to continue... |
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 tested for what @khaes-kth said. It reports no violation.
I also suggested a change in the test files. I am sorry to propose these changes after I said the last ones were my final. 😅
@Override | ||
protected void repairInternal(CtNewClass<?> newClass) { | ||
CtClass<?> innerClass = newClass.getAnonymousClass(); | ||
if (hasNoFields(innerClass) && hasOnlyConstructorAndSingleMethod(innerClass)) { |
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.
Apparently, we don't need these conditions as well. As @khaes-kth suggested, I checked if SonarLint reports a violation if there are more than 2 type members. It does not. For example, the following snippet does not report any sonar violation.
ThreadLocal<List<String>> myThreadLocal = new ThreadLocal<>() {
private static final String X = "1";
@Override
protected List<String> initialValue() {
System.setProperty(X, "abc");
return new ArrayList<>();
}
};
@@ -0,0 +1,13 @@ | |||
|
|||
public class ThreadLocalInitial { |
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.
We can rename this, I think.
public class ThreadLocalInitial { | |
public class ThreadLocalInitialWithExactlyOneReturnStatement { |
@@ -0,0 +1,14 @@ | |||
|
|||
public class ThreadLocalInitial2 { |
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.
We can rename this also.
public class ThreadLocalInitial2 { | |
public class ThreadLocalInitialWithMultipleStatements { |
Thanks for testing it! We just used SonarLint with IntelliJ. |
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.
Looks good to me!
@khaes-kth you can add more comments or proceed to merge. |
Shouldn't we also update the handled rules doc? |
It will happen automatically once you merge this PR :) GHA will create a PR for that. |
newClass.replace(invocation); | ||
} | ||
|
||
private CtInvocation<?> createinitialMethod(CtNewClass<?> threadLocal, CtLambda<?> lambda) { |
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.
Shouldn't be createInitialMethod
? (typo on first character of initial
)
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.
@MartinWitt I think I overlooked this while suggesting the typographical error.
Cool. Didn't remember 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.
LGTM!
super cool! |
This code is more or less copied from laughing-train and worked on spoon already, see https://github.com/INRIA/spoon/pull/4540/files. I would be surprised if we find this sonar rule violation anywhere else.