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

feature: new rule use-new

This commit is contained in:
chavacava
2025-09-28 08:08:08 +02:00
parent c037639bc0
commit 8aae877599
7 changed files with 114 additions and 0 deletions

View File

@@ -116,6 +116,7 @@ var allRules = append([]lint.Rule{
&rule.InefficientMapLookupRule{},
&rule.ForbiddenCallInWgGoRule{},
&rule.UnnecessaryIfRule{},
&rule.UseNewRule{},
}, defaultRules...)
// allFormatters is a list of all available formatters to output the linting results.

View File

@@ -44,6 +44,8 @@ var (
Go124 = goversion.Must(goversion.NewVersion("1.24"))
// Go125 is a constant representing the Go version 1.25.
Go125 = goversion.Must(goversion.NewVersion("1.25"))
// Go126 is a constant representing the Go version 1.26.
Go126 = goversion.Must(goversion.NewVersion("1.26"))
)
// Files return package's files.

85
rule/use_new.go Normal file
View File

@@ -0,0 +1,85 @@
package rule
import (
"fmt"
"go/ast"
"github.com/mgechev/revive/internal/astutils"
"github.com/mgechev/revive/lint"
)
// UseNewRule implements a rule that proposes using new(expr) when possible.
type UseNewRule struct{}
// Apply applies the rule to given file.
func (r *UseNewRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
if !file.Pkg.IsAtLeastGoVersion(lint.Go126) {
return nil
}
var failures []lint.Failure
for _, decl := range file.AST.Decls {
funcDecl, ok := decl.(*ast.FuncDecl)
if !ok || funcDecl.Body == nil {
continue
}
failures = append(failures, r.lintFunction(funcDecl)...)
}
return failures
}
// Name returns the rule name.
func (*UseNewRule) Name() string {
return "use-new"
}
func (r *UseNewRule) lintFunction(funcDecl *ast.FuncDecl) []lint.Failure {
if !r.isNewValueFunc(funcDecl) {
return nil
}
return []lint.Failure{
{
Failure: fmt.Sprintf(`calls to "%s(value)" can be replaced by a call to "new(value)"`, funcDecl.Name.Name),
Confidence: 1,
Node: funcDecl,
Category: lint.FailureCategoryOptimization,
},
}
}
// isNewValueFunc checks if the function is of the form:
//
// func foo(p Type) *Type {
// return &p
// }
func (*UseNewRule) isNewValueFunc(funcDecl *ast.FuncDecl) bool {
if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
return false // not one return value
}
if funcDecl.Type.Params == nil || len(funcDecl.Type.Params.List) != 1 {
return false // not one parameter
}
if len(funcDecl.Body.List) != 1 {
return false // not one statement
}
paramTypes := astutils.GetTypeNames(funcDecl.Type.Params)
resultTypes := astutils.GetTypeNames(funcDecl.Type.Results)
if "*"+paramTypes[0] != resultTypes[0] {
return false // return type is not pointer to parameter type
}
retStmt, ok := funcDecl.Body.List[0].(*ast.ReturnStmt)
if !ok || len(retStmt.Results) != 1 {
return false // not a return statement with one result
}
returnExpr := astutils.GoFmt(retStmt.Results[0]) // TODO use more efficient way to retrieve the id
return returnExpr == "&"+funcDecl.Type.Params.List[0].Names[0].Name
}

13
test/use_new_test.go Normal file
View File

@@ -0,0 +1,13 @@
package test
import (
"testing"
"github.com/mgechev/revive/lint"
"github.com/mgechev/revive/rule"
)
func TestUseNew(t *testing.T) {
testRule(t, "use_new", &rule.UseNewRule{}, &lint.RuleConfig{})
testRule(t, "go1.26/use_new", &rule.UseNewRule{}, &lint.RuleConfig{})
}

3
testdata/go1.26/go.mod vendored Normal file
View File

@@ -0,0 +1,3 @@
module github.com/mgechev/revive/testdata
go 1.26

5
testdata/go1.26/use_new.go vendored Normal file
View File

@@ -0,0 +1,5 @@
package fixtures
func useNewOne(a int) *int { // MATCH /calls to "useNewOne(value)" can be replaced by a call to "new(value)"/
return &a
}

5
testdata/use_new.go vendored Normal file
View File

@@ -0,0 +1,5 @@
package fixtures
func useNewOne(a int) *int {
return &a
}