Use "slices.Equal()" instead of custom implementation of this function#100
Use "slices.Equal()" instead of custom implementation of this function#100paulnegz wants to merge 1 commit intoSonarSource:masterfrom
Conversation
This commit introduces a new check to detect manually implemented slice equality functions and suggest using the standard `slices.Equal()` function instead. The `SliceEqualSimplificationCheck` identifies functions that have a specific structure: - Two slice parameters - A length comparison - A for-loop for element-wise comparison - A final `return true` statement This helps improve code consistency and maintainability by encouraging the use of the standard library. Comprehensive tests have been added to cover various positive and negative cases to ensure the check is accurate.
| @@ -0,0 +1,192 @@ | |||
| /* | |||
There was a problem hiding this comment.
Please move this file to: sonar-go-checks/src/main/java/org/sonar/go/checks/SliceEqualSimplificationCheck.java
| @@ -0,0 +1,27 @@ | |||
| /* | |||
There was a problem hiding this comment.
Please move this file to: sonar-go-checks/src/test/java/org/sonar/go/checks/SliceEqualSimplificationCheckTest.java
| @@ -0,0 +1,100 @@ | |||
| // Positive cases | |||
There was a problem hiding this comment.
Please move this file to sonar-go-checks/src/test/resources/checks/SliceEqualSimplification/SliceEqualSimplification.go
(There is go extension instead of slang). Now in sonar-go we are not using *.slang files anymore but Go code.
There was a problem hiding this comment.
Please add package SliceEqualSimplification at the beginning of the file. Otherwise, the file will be not parsed.
There was a problem hiding this comment.
Please use Sensitive cases instead of Positive cases
| import org.sonarsource.slang.checks.api.SlangCheck; | ||
|
|
||
| @Rule(key = "S-slice-equal") | ||
| public class SliceEqualSimplificationCheck implements SlangCheck { |
There was a problem hiding this comment.
| public class SliceEqualSimplificationCheck implements SlangCheck { | |
| public class SliceEqualSimplificationCheck implements GoCheck { |
| // Semantic checks would be needed here to verify types. | ||
| // For example: | ||
| // if (!isSlice(param1.type()) || !isSlice(param2.type())) { | ||
| // return false; | ||
| // } | ||
| // if (!areElementTypesComparableAndIdentical(param1.type(), param2.type())) { | ||
| // return false; | ||
| // } |
There was a problem hiding this comment.
For now it can be approximated with:
private static boolean areParametersSlices(ParameterTree param1, ParameterTree param2) {
return param1.type().type().equals(param2.type().type());
}
However it requires more complex logic, to cover some cases:
func equalSlicesWithDifferentVarNames(slice1, slice2 []int) bool- first type is skippedfunc Equal[S ~[]E, E comparable](s1, s2 S) bool- original signature from Go standard libraryfunc (rec *MyStruct) equalSlicesReceiver(slice1, slice2 []int) bool- what if there is receiver?func equalSlicesFirstParamAsPointer(s1 *[]int, s2 []int) bool- what if there is a pointer?- maybe some other possible cases?
Please try to cover them.
| private static boolean isReturnFalse(Tree statement) { | ||
| if (!(statement instanceof ReturnTree)) { | ||
| return false; | ||
| } | ||
| ReturnTree returnTree = (ReturnTree) statement; | ||
| Tree body = returnTree.body(); | ||
| return body instanceof IdentifierTree && "false".equals(((IdentifierTree) body).name()); | ||
| } | ||
|
|
||
| private static boolean isReturnTrue(Tree statement) { | ||
| if (!(statement instanceof ReturnTree)) { | ||
| return false; | ||
| } | ||
| ReturnTree returnTree = (ReturnTree) statement; | ||
| Tree body = returnTree.body(); | ||
| return body instanceof IdentifierTree && "true".equals(((IdentifierTree) body).name()); | ||
| } |
There was a problem hiding this comment.
| private static boolean isReturnFalse(Tree statement) { | |
| if (!(statement instanceof ReturnTree)) { | |
| return false; | |
| } | |
| ReturnTree returnTree = (ReturnTree) statement; | |
| Tree body = returnTree.body(); | |
| return body instanceof IdentifierTree && "false".equals(((IdentifierTree) body).name()); | |
| } | |
| private static boolean isReturnTrue(Tree statement) { | |
| if (!(statement instanceof ReturnTree)) { | |
| return false; | |
| } | |
| ReturnTree returnTree = (ReturnTree) statement; | |
| Tree body = returnTree.body(); | |
| return body instanceof IdentifierTree && "true".equals(((IdentifierTree) body).name()); | |
| } | |
| private static boolean isReturnFalse(Tree statement) { | |
| return isReturn(statement, "false"); | |
| } | |
| private static boolean isReturnTrue(Tree statement) { | |
| return isReturn(statement, "true"); | |
| } | |
| private static boolean isReturn(Tree statement, String returnValue) { | |
| if (statement instanceof ReturnTree returnTree && | |
| returnTree.expressions().size() == 1) { | |
| Tree body = returnTree.expressions().get(0); | |
| return body instanceof LiteralTree LiteralTreeBody && returnValue.equals(LiteralTreeBody.value()); | |
| } | |
| return false; | |
| } |
|
|
||
| @Test | ||
| void test() { | ||
| Verifier.verify("SliceEqualSimplification.slang", new SliceEqualSimplificationCheck()); |
There was a problem hiding this comment.
| Verifier.verify("SliceEqualSimplification.slang", new SliceEqualSimplificationCheck()); | |
| GoVerifier.verify("SliceEqualSimplification/SliceEqualSimplification.go", new SliceEqualSimplificationCheck()); |
| @@ -0,0 +1,100 @@ | |||
| // Positive cases | |||
| fun equalSlices(a, b) { // Noncompliant {{Use slices.Equal() instead of custom slice comparison}} | |||
There was a problem hiding this comment.
Please add:
// ^^^^^^^^^^^
in next line, to indicate that the issue should be only raised on the method name.
| } | ||
| return true | ||
| } | ||
|
|
There was a problem hiding this comment.
Please add following cases:
// function with generics like in slices.Equal()
func Equal[S ~[]E, E comparable](s1, s2 S) bool { // Noncompliant {{Use slices.Equal() instead of custom slice comparison}}
if len(s1) != len(s2) {
return false
}
for i := range s1 {
if s1[i] != s2[i] {
return false
}
}
return true
}
func (rec *MyStruct) equalSlicesReceiver(slice1, slice2 []int) bool { // Noncompliant {{Use slices.Equal() instead of custom slice comparison}}
if len(slice1) != len(slice2) {
return false
}
for i := range slice1 {
if slice1[i] != slice2[i] {
return false
}
}
return true
}
func equalSlicesFirstParamAsPointer(s1 *[]int, s2 []int) bool { // Noncompliant {{Use slices.Equal() instead of custom slice comparison}}
if len(*s1) != len(s2) {
return false
}
for i := range *s1 {
if (*s1)[i] != s2[i] {
return false
}
}
return true
}
And maybe you can think up more cases.
| } | ||
|
|
||
| // Negative cases - should not be flagged | ||
| fun notEqualSlicesWrongLenCheck(a, b) { |
There was a problem hiding this comment.
Please change this code to Go code.
|
Hi @paulnegz ! |
This commit introduces a new check to detect manually implemented slice equality functions and suggest using the standard
slices.Equal()function instead.The
SliceEqualSimplificationCheckidentifies functions that have a specific structure:return truestatementThis helps improve code consistency and maintainability by encouraging the use of the standard library. Comprehensive tests have been added to cover various positive and negative cases to ensure the check is accurate.
Part of