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

SONARKT-301 - Implement rule S6524: collection should be immutable #390

Merged
merged 10 commits into from
Nov 27, 2023

Conversation

erwan-serandour-sonarsource

No description provided.

"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",

Choose a reason for hiding this comment

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

add new clean code taxonomy

Copy link
Contributor

Choose a reason for hiding this comment

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

In terms of how we go about visiting the AST, it looks fine on first glance. Although perhaps it is worth noting that we currently do not look at properties defined outside of functions (private class & file-level properties). Was that intended?

In terms of determining if a collection has been written to, so needs to be mutable, I think we need to improve the logic a bit.

The current logic assumes that a collection is not written to by default and only if one of the listed funs is called, do we recognize it as written to. If I define a custom collection extension fun or one that takes a collection as argument, which writes to the respective collection, we will miss the fact that this function is a writing function everywhere else. We can probably depend on the type information instead, to see if a collection is only used in contexts where an immutable type is required. However, if we don't understand what a call to a function does w.r.t. the collection argument/receiver, we should assume it is modified to avoid FPs. In other words, our default assumption should be "the collection IS modified" and we need to prove otherwise to raise an issue.

In general, I strongly recommend focusing more on the test cases. In fact, for rules like this, it helps to really go with TDD and think of all kinds of crazy ways you could modify collections first, even before you start writing the rule. Then start simple and see how that tricks the logic you write, accommodating the complexities bit by bit. Especially if you're not sure of the logic, write more test cases! Have some complex examples in there, where you have functions that call functions or where you pass function calls as an argument to another function call. Use .let, with(), etc. This way you have to depend way less on the PR review and ultimately it will save you some time too, because of less back-and-forth.

Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed

Comment on lines 175 to 173
val receiverFullyQualifiedName = if (receiverType?.constructor?.declarationDescriptor != null) {
receiverType.getKotlinTypeFqName(false)
} else {
null
}

Choose a reason for hiding this comment

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

You can directly return from this if-else, no need in saving the result in a val, just to check it on the next line.

}
}

private fun KtQualifiedExpression.mutateCollection(bindingContext: BindingContext): Boolean {

Choose a reason for hiding this comment

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

These mutateCollection functions are called like this because they filter for expressions that mutate a collection, correct? I would call them mutatesCollection with an s, to make that even more clear.

Comment on lines 68 to 70
val isExtensionFunctionOnMutableCollection =
function.receiverTypeReference?.determineTypeAsString(bindingContext) in mutableCollections
if (isExtensionFunctionOnMutableCollection) {

Choose a reason for hiding this comment

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

I think it would be readable in this instance to get rid of the val and move the condition into the if (...) directly.

Comment on lines 81 to 84
val mutableParameters: List<KtNamedDeclaration> = function.valueParameters
.filter { it.isMutableCollection(bindingContext) }

mutableParameters

Choose a reason for hiding this comment

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

You can chain the calls directly, no need for the val.

Comment on lines 116 to 112
private fun KtCallExpression.noReceiver(): Boolean {
return this.parent is KtBlockExpression
}

Choose a reason for hiding this comment

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

I think this may be related to this, we currently raise issues where we shouldn't. E.g.:

    fun MutableList<Int>.compliantDelegate(): Int { // Compliant
        if(add(1)) {}
        return reduce { acc, it -> acc + it }
    }

Copy link

Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed today, let's double-check the results during verification later today.

@johann-beleites-sonarsource johann-beleites-sonarsource merged commit 54506e1 into master Nov 27, 2023
9 checks passed
@johann-beleites-sonarsource johann-beleites-sonarsource deleted the erwan/ImmutableCollection branch November 27, 2023 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants