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

[WIP] New rule use-errors-new (propose replacing fmt.Errorf by errors.New when possible) #1136

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
| [`file-length-limit`](./RULES_DESCRIPTIONS.md#file-length-limit) | map (optional)| Enforces a maximum number of lines per file | no | no |
| [`filename-format`](./RULES_DESCRIPTIONS.md#filename-format) | regular expression (optional) | Enforces the formatting of filenames | no | no |
| [`redundant-build-tag`](./RULES_DESCRIPTIONS.md#redundant-build-tag) | n/a | Warns about redundant `// +build` comment lines | no | no |
| [`use-errors-new`](./RULES_DESCRIPTIONS.md#use-errors-new) | n/a | Spots calls to `fmt.Errorf` that can be replaced by `errors.New` | no | no |

## Configurable rules

Expand Down
8 changes: 8 additions & 0 deletions RULES_DESCRIPTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ List of all available rules.
- [unused-parameter](#unused-parameter)
- [unused-receiver](#unused-receiver)
- [use-any](#use-any)
- [use-errors-new](#use-errors-new)
- [useless-break](#useless-break)
- [var-declaration](#var-declaration)
- [var-naming](#var-naming)
Expand Down Expand Up @@ -987,6 +988,13 @@ _Description_: Since Go 1.18, `interface{}` has an alias: `any`. This rule propo

_Configuration_: N/A

## use-errors-new

_Description_: This rules identifies calls to `fmt.Errorf` that can be safely replaced by, the more efficient, `errors.New`.

_Configuration_: N/A


## useless-break

_Description_: This rule warns on useless `break` statements in case clauses of switch and select statements. Go, unlike other programming languages like C, only executes statements of the selected case while ignoring the subsequent case clauses.
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@
&rule.CommentsDensityRule{},
&rule.FileLengthLimitRule{},
&rule.FilenameFormatRule{},
&rule.RedundantBuildTagRule{},

Check failure on line 101 in config/config.go

View workflow job for this annotation

GitHub Actions / Tests on Windows

undefined: rule.RedundantBuildTagRule

Check failure on line 101 in config/config.go

View workflow job for this annotation

GitHub Actions / Tests on Unix (oldstable)

undefined: rule.RedundantBuildTagRule

Check failure on line 101 in config/config.go

View workflow job for this annotation

GitHub Actions / Tests on Unix (stable)

undefined: rule.RedundantBuildTagRule
&rule.UseErrorsNewRule{},
}, defaultRules...)

var allFormatters = []lint.Formatter{
Expand Down
41 changes: 0 additions & 41 deletions rule/redundant_build_tag.go

This file was deleted.

60 changes: 60 additions & 0 deletions rule/use_errors_new.go
Copy link
Contributor

Choose a reason for hiding this comment

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

No test?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As I've said in the PR description, it is work in progress, I'll complete the PR (tests+doc) in the next days.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

tests and doc added

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package rule

import (
"go/ast"

"github.com/mgechev/revive/lint"
)

// UseErrorsNewRule spots calls to fmt.Errorf that can be replaced by errors.New.
type UseErrorsNewRule struct{}

// Apply applies the rule to given file.
func (*UseErrorsNewRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure

walker := lintFmtErrorf{
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
}

ast.Walk(walker, file.AST)

return failures
}

// Name returns the rule name.
func (*UseErrorsNewRule) Name() string {
return "use-errors-new"
}

type lintFmtErrorf struct {
onFailure func(lint.Failure)
}

func (w lintFmtErrorf) Visit(n ast.Node) ast.Visitor {
funcCall, ok := n.(*ast.CallExpr)
if !ok {
return w // not a function call
}

isFmtErrorf := isPkgDot(funcCall.Fun, "fmt", "Errorf")
if !isFmtErrorf {
return w // not a call to fmt.Errorf
}

if len(funcCall.Args) > 1 {
return w // the use of fmt.Errorf is legit
Copy link
Contributor

@ccoVeille ccoVeille Nov 20, 2024

Choose a reason for hiding this comment

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

Well, not exactly

We have crazy people out there

fmt.Errorf("%s, "whatever")
https://github.com/search?q=language%3Ago++%22fmt.Errorf%28%5C%22%25s%5C%22%2C+%5C%22%22&type=code

fmt.Errorf("%w", err)
https://github.com/search?q=language%3Ago++%22fmt.Errorf%28%5C%22%25w%5C%22%2C+%22&type=code

I created an issue on perfprint linter, as it's in their scope

I think you can merge like this, with current code.

My point can be addressed later

Copy link
Contributor

Choose a reason for hiding this comment

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

Discussion moved on the PR

#1142 (comment)

}

// the call is of the form fmt.Errorf("...")
w.onFailure(lint.Failure{
Category: "errors",
Node: n,
Confidence: 1,
Failure: "replace fmt.Errorf by errors.New",
})

return w
}
11 changes: 11 additions & 0 deletions test/use_errors_new_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package test

import (
"testing"

"github.com/mgechev/revive/rule"
)

func TestUseErrorsNew(t *testing.T) {
testRule(t, "use_errors_new", &rule.UseErrorsNewRule{})
}
12 changes: 12 additions & 0 deletions testdata/use_errors_new.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package pkg

import "fmt"

func errorsNew() (int, error) {
fmt.Errorf("repo cannot be nil") // MATCH /replace fmt.Errorf by errors.New/
errs := append(errs, fmt.Errorf("commit cannot be nil")) // MATCH /replace fmt.Errorf by errors.New/
fmt.Errorf("unable to load base repo: %w", err)
fmt.Errorf("Failed to get full commit id for origin/%s: %w", pr.BaseBranch, err)

return 0, fmt.Errorf(msg + "something") // MATCH /replace fmt.Errorf by errors.New/
}
Loading