1
0
mirror of https://github.com/mgechev/revive.git synced 2025-10-30 23:37:49 +02:00

modifies-parameter: handle slices.Delete and slices.DeleteFunc (#1427)

This commit is contained in:
Baranov Victor
2025-07-22 14:21:34 +03:00
committed by GitHub
parent 40a690dff5
commit b969b8be4a
3 changed files with 111 additions and 4 deletions

View File

@@ -873,7 +873,8 @@ arguments = [3]
_Description_: A function that modifies its parameters can be hard to understand.
It can also be misleading if the arguments are passed by value by the caller.
This rule warns when a function modifies one or more of its parameters.
This rule warns when a function modifies one or more of its parameters or when
parameters are passed to functions that modify them (e.g. `slices.Delete`).
_Configuration_: N/A

View File

@@ -4,12 +4,22 @@ import (
"fmt"
"go/ast"
"github.com/mgechev/revive/internal/astutils"
"github.com/mgechev/revive/lint"
)
// ModifiesParamRule warns on assignments to function parameters.
type ModifiesParamRule struct{}
// modifyingParamPositions are parameter positions that are modified by a function.
type modifyingParamPositions = []int
// modifyingFunctions maps function names to the positions of parameters they modify.
var modifyingFunctions = map[string]modifyingParamPositions{
"slices.Delete": {0},
"slices.DeleteFunc": {0},
}
// Apply applies the rule to given file.
func (*ModifiesParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
@@ -57,12 +67,19 @@ func (w lintModifiesParamRule) Visit(node ast.Node) ast.Visitor {
}
case *ast.AssignStmt:
lhs := v.Lhs
for _, e := range lhs {
for i, e := range lhs {
id, ok := e.(*ast.Ident)
if ok {
checkParam(id, &w)
if !ok {
continue
}
if i < len(v.Rhs) {
w.checkModifyingFunction(v.Rhs[i])
}
checkParam(id, &w)
}
case *ast.ExprStmt:
w.checkModifyingFunction(v.X)
}
return w
@@ -78,3 +95,39 @@ func checkParam(id *ast.Ident, w *lintModifiesParamRule) {
})
}
}
func (w *lintModifiesParamRule) checkModifyingFunction(callNode ast.Node) {
callExpr, ok := callNode.(*ast.CallExpr)
if !ok {
return
}
funcName := astutils.GoFmt(callExpr.Fun)
positions, found := modifyingFunctions[funcName]
if !found {
return
}
for _, pos := range positions {
if pos >= len(callExpr.Args) {
return
}
id, ok := callExpr.Args[pos].(*ast.Ident)
if !ok {
continue
}
_, match := w.params[id.Name]
if !match {
continue
}
w.onFailure(lint.Failure{
Confidence: 0.5, // confidence is low because of shadow variables
Node: callExpr,
Category: lint.FailureCategoryBadPractice,
Failure: fmt.Sprintf("parameter '%s' seems to be modified by '%s'", id.Name, funcName),
})
}
}

View File

@@ -1,5 +1,7 @@
package fixtures
import "slices"
func one(a int) {
a, b := 1, 2 // MATCH /parameter 'a' seems to be modified/
a++ // MATCH /parameter 'a' seems to be modified/
@@ -23,3 +25,54 @@ func three(s *foo) {
func issue355(_ *foo) {
_ = "foooooo"
}
func testSlicesDeleteAssigned(s []int) {
s = slices.Delete(s, 0, 1)
// MATCH:30 /parameter 's' seems to be modified/
// MATCH:30 /parameter 's' seems to be modified by 'slices.Delete'/
s = slices.DeleteFunc(s, func(e string) bool { return true })
// MATCH:33 /parameter 's' seems to be modified/
// MATCH:33 /parameter 's' seems to be modified by 'slices.DeleteFunc'/
_ = slices.Delete(s, 0, 1) // MATCH /parameter 's' seems to be modified by 'slices.Delete'/
_ = slices.DeleteFunc(s, func(e string) bool { // MATCH /parameter 's' seems to be modified by 'slices.DeleteFunc'/
return true
})
s, b := slices.Delete(s, 0, 1), 2
// MATCH:40 /parameter 's' seems to be modified/
// MATCH:40 /parameter 's' seems to be modified by 'slices.Delete'/
s := slices.Clone(s)
// MATCH:43 /parameter 's' seems to be modified/
}
func testSlicesDeleteCloned(s []int) {
s2 := slices.Clone(s)
s2 = slices.Delete(s2, 0, 1)
_ = slices.Delete(slices.Clone(s), 0, 1)
_ = slices.DeleteFunc(slices.Clone(s), func(e string) bool {
return e == "test"
})
}
func testSlicesDeleteCopied(s []int) {
c := make([]int, len(s))
copy(c, s)
c = slices.Delete(c, 0, 1)
}
func testMultipleParams(a, b, s []int) {
a = slices.Delete(a, 0, 1)
// MATCH:63 /parameter 'a' seems to be modified/
// MATCH:63 /parameter 'a' seems to be modified by 'slices.Delete'/
b = slices.Delete(b, 1, 2)
// MATCH:66 /parameter 'b' seems to be modified/
// MATCH:66 /parameter 'b' seems to be modified by 'slices.Delete'/
s = []int{1, 2, 3} // MATCH /parameter 's' seems to be modified/
s = slices.Delete(s, 0, 1)
// MATCH:70 /parameter 's' seems to be modified/
// MATCH:70 /parameter 's' seems to be modified by 'slices.Delete'/
}
func testAssignToNewVar(s []int) {
newSlice := s
newSlice = slices.Delete(newSlice, 0, 1)
}