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

Extend VarCouldBeVal to include analysis of file- and class-level properties #4424

Merged
merged 6 commits into from Jan 5, 2022

Conversation

severn-everett
Copy link
Contributor

@severn-everett severn-everett commented Dec 29, 2021

Second poor soul attempting to conquer #1330. Also, I noticed what could be a potential false-positive issue for var properties declared in object literals that escape their containing declaration, so this code includes checks for that.

…l properties; Added checks in VarCouldBeVal to avoid flagging escaped object literals
@codecov
Copy link

codecov bot commented Dec 29, 2021

Codecov Report

Merging #4424 (34b5cc2) into main (21a751a) will increase coverage by 0.00%.
The diff coverage is 89.23%.

Impacted file tree graph

@@            Coverage Diff            @@
##               main    #4424   +/-   ##
=========================================
  Coverage     84.34%   84.34%           
+ Complexity     3299     3297    -2     
=========================================
  Files           473      473           
  Lines         10532    10580   +48     
  Branches       1885     1906   +21     
=========================================
+ Hits           8883     8924   +41     
  Misses          671      671           
- Partials        978      985    +7     
Impacted Files Coverage Δ
...b/arturbosch/detekt/core/reporting/OutputFacade.kt 90.90% <ø> (ø)
...lab/arturbosch/detekt/rules/style/VarCouldBeVal.kt 87.50% <88.52%> (-2.09%) ⬇️
...bosch/detekt/rules/complexity/MethodOverloading.kt 90.90% <100.00%> (ø)
...etekt/rules/complexity/StringLiteralDuplication.kt 95.74% <100.00%> (ø)
...turbosch/detekt/rules/style/UnusedPrivateMember.kt 92.68% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 21a751a...34b5cc2. Read the comment docs.

@severn-everett
Copy link
Contributor Author

severn-everett commented Dec 29, 2021

First issue identified:

[reports] at /home/runner/work/detekt/detekt/detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/CliArgs.kt:78:5

I'm not sure what kind of "tell" there would be for this one, given it's marked as private and not overtly modified in the class. It seems like this would be a place to add a suppression annotation. Thoughts?

@schalkms
Copy link
Member

[reports] at /home/runner/work/detekt/detekt/detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/CliArgs.kt:78:5

I guess reports can be made public as all other fields in this class or internal.

Comment on lines 72 to 73
if (bindingContext == BindingContext.EMPTY) return
super.visitKtFile(file)
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
if (bindingContext == BindingContext.EMPTY) return
super.visitKtFile(file)
super.visitKtFile(file)
if (bindingContext == BindingContext.EMPTY) return

Copy link
Member

Choose a reason for hiding this comment

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

If there's no bindingContext available, the super method isn't called.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe that's desirable, right? Faster rule.

I think that we should even move this checks earlier in the life cycle. But that's another topic.

Copy link
Member

Choose a reason for hiding this comment

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

In this case, you don't gain any performance benefit, because KtFile's are usually not nested.

I agree with the following statement. In the best case, you don't even load the rule if it's annotated with the corresponding annotation. As far as I remember, there's an issue floating around, which discusses this topic. Anyways, as you said, this is out of scope for this PR.

I think that we should even move this checks earlier in the life cycle. But that's another topic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's cargo-culting from other files that I copied from when I started out working on this code base. I think it and the test wording show that there might need to be some more definitions in the contribution guide about style and things to add or avoid.

Copy link
Member

@schalkms schalkms left a comment

Choose a reason for hiding this comment

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

Looks good! 👍
Thank you very much for tackling this really rule improvement!

it("does not report non-private variables") {
val code = """
var a = 1
""".trimIndent()
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: if you don't test for characters or reports at a specific position in the code snippet, it's optional to include the trimIndent() call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My IDE automatically appends .trimIndent() when I create these string blocks. If that's a setting that's generally better left off, I'll disable it, but I'd rather keep things as-is if they don't cause any harm.

@@ -13,6 +13,82 @@ class VarCouldBeValSpec : Spek({
val env: KotlinCoreEnvironment by memoized()
val subject by memoized { VarCouldBeVal() }

describe("file-level declarations") {
it("does not report non-private variables") {
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
it("does not report non-private variables") {
it("does not report public variables") {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

public != non-private - there are protected and internal scopes as well. I didn't do it because I felt it'd be a bit excessive, but I could put a few more tests to cover the other scopes.

}

describe("class-level declarations") {
it("does not report non-private variables in non-private classes") {
Copy link
Member

Choose a reason for hiding this comment

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

I guess the following is a bit easier to read and grasp.

Suggested change
it("does not report non-private variables in non-private classes") {
it("does not report public variables in public classes") {

assertThat(subject.compileAndLintWithContext(env, code)).isEmpty()
}

it("does not report non-private variables in non-private objects") {
Copy link
Member

Choose a reason for hiding this comment

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

same here

Comment on lines 274 to 275
context("should not report anonymous objects that escape") {
it("when initializing a variable directly") {
Copy link
Member

Choose a reason for hiding this comment

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

minor: I think it might be beneficial to apply the same test description style as above. I guess it's easier to grasp the many different cases when reading and editing this rule in the future.
e.g.:

context("anonymous objects that escape") {
    it("does not report when initializing a variable directly") {
    }

    it("does not report")...

@BraisGabin
Copy link
Member

[reports] at /home/runner/work/detekt/detekt/detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/CliArgs.kt:78:5

Other option is to add an ingnoreAnnotated and ignore the params that had the @param annotation. For sure I'm talking about adding that configuration in our own configuration, not as a default one.

Copy link
Member

@BraisGabin BraisGabin left a comment

Choose a reason for hiding this comment

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

Nice one! The next RC will have a lot a juice! Thanks!!

import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall

private val unaryAssignmentOperators = setOf(KtTokens.MINUSMINUS, KtTokens.PLUSPLUS)
import org.jetbrains.kotlin.util.containingNonLocalDeclaration

/**
* Reports var declarations (locally-scoped variables) that could be val, as they are not re-assigned.
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
* Reports var declarations (locally-scoped variables) that could be val, as they are not re-assigned.
* Reports var declarations (both local variables and private class properties) that could be val, as they are not re-assigned.

private fun KtProperty.isDeclarationCandidate(): Boolean {
return when {
!isVar -> false
isLocal || isPrivate() -> true
Copy link
Member

Choose a reason for hiding this comment

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

It might be good to follow up with hanlding expect / actual properties.

@chao2zhang
Copy link
Member

Thanks for the contribution!

@chao2zhang chao2zhang merged commit c94a5d9 into detekt:main Jan 5, 2022
@cortinico cortinico added this to the 1.20.0 milestone Jan 5, 2022
@cortinico cortinico added the rules label Jan 5, 2022
@severn-everett severn-everett deleted the extend_VarCouldBeVal branch January 10, 2022 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants