diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0ffccc3..33b10de 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,4 +4,4 @@ updates: - package-ecosystem: gomod directory: "/" schedule: - interval: "daily" + interval: "monthly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce12a0f..5588bb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,9 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: rlespinasse/github-slug-action@v4 - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: - go-version: ^1.20 + go-version: ^1.21 - run: cd /tmp && go install github.com/Antonboom/errname@${{ env.GITHUB_REF_NAME }} && errname -h lint: @@ -22,14 +22,15 @@ jobs: - uses: actions/checkout@v3 - uses: golangci/golangci-lint-action@v3 with: - version: latest + version: v1.54.0 + args: --timeout=5m tests: runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: - go-version: ^1.20 + go-version: ^1.21 - uses: actions/checkout@v3 - run: go test -coverprofile=coverage.out ./... - uses: shogo82148/actions-goveralls@v1 diff --git a/.golangci.yml b/.golangci.yml index 8b75bc3..8e7ebf9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,7 +18,6 @@ linters: - asciicheck - bidichk - bodyclose - - depguard - dogsled - dupl - durationcheck @@ -53,6 +52,7 @@ linters: - lll - makezero - misspell + - mirror - nakedret - nilerr - nilnil diff --git a/README.md b/README.md index 1eb772a..8fc8203 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ var ErrInvalidURL = errors.New("invalid url") // or errInvalidURL More examples in [tests](https://github.com/Antonboom/errname/blob/master/pkg/analyzer/facts_test.go). -## Assumptions +## Assumptions (open to contributors 🙏🏻)
Click to expand @@ -112,6 +112,18 @@ func NewDecodeError() error { - Package aliases are not supported if the source package and its directory differ in name. +- Nested error types are not supported + +```go +type timeoutErr struct { // no warning from the linter :( + error +} + +type DeadlineErr struct { // no warning from the linter :( + timeoutErr +} +``` + - Not supported sentinel errors that were created by an external type or func (except `errors`/`fmt`) and that do not have an explicit type `error`: diff --git a/Taskfile.yml b/Taskfile.yml index b1e6df3..bbdcb5c 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -15,8 +15,8 @@ tasks: tools:install: - echo "Install local tools..." - - (which gci > /dev/null) || GO111MODULE=off go install github.com/daixiang0/gci@latest - - (which gofumpt > /dev/null) || GO111MODULE=off go install mvdan.cc/gofumpt@latest + - (which gci > /dev/null) || go install github.com/daixiang0/gci@latest + - (which gofumpt > /dev/null) || go install mvdan.cc/gofumpt@latest tidy: cmds: diff --git a/go.mod b/go.mod index c53a034..6aa912c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Antonboom/errname -go 1.20 +go 1.21 require ( golang.org/x/sys v0.11.0 // indirect diff --git a/go.sum b/go.sum index 14a6610..a851954 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 6425db1..aa85225 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -1,6 +1,7 @@ package analyzer import ( + "fmt" "go/ast" "go/token" "strconv" @@ -25,16 +26,16 @@ func New() *analysis.Analyzer { type stringSet = map[string]struct{} var ( - imports = []ast.Node{(*ast.ImportSpec)(nil)} - types = []ast.Node{(*ast.TypeSpec)(nil)} - funcs = []ast.Node{(*ast.FuncDecl)(nil)} + importNodes = []ast.Node{(*ast.ImportSpec)(nil)} + typeNodes = []ast.Node{(*ast.TypeSpec)(nil)} + funcNodes = []ast.Node{(*ast.FuncDecl)(nil)} ) func run(pass *analysis.Pass) (interface{}, error) { insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) pkgAliases := map[string]string{} - insp.Preorder(imports, func(node ast.Node) { + insp.Preorder(importNodes, func(node ast.Node) { i := node.(*ast.ImportSpec) if n := i.Name; n != nil && i.Path != nil { if path, err := strconv.Unquote(i.Path.Value); err == nil { @@ -45,14 +46,14 @@ func run(pass *analysis.Pass) (interface{}, error) { allTypes := stringSet{} typesSpecs := map[string]*ast.TypeSpec{} - insp.Preorder(types, func(node ast.Node) { + insp.Preorder(typeNodes, func(node ast.Node) { t := node.(*ast.TypeSpec) allTypes[t.Name.Name] = struct{}{} typesSpecs[t.Name.Name] = t }) errorTypes := stringSet{} - insp.Preorder(funcs, func(node ast.Node) { + insp.Preorder(funcNodes, func(node ast.Node) { f := node.(*ast.FuncDecl) t, ok := isMethodError(f) if !ok { @@ -62,7 +63,7 @@ func run(pass *analysis.Pass) (interface{}, error) { tSpec, ok := typesSpecs[t] if !ok { - panic("no specification for type " + t) + panic(fmt.Sprintf("no specification for type %q", t)) } if _, ok := tSpec.Type.(*ast.ArrayType); ok { @@ -75,7 +76,7 @@ func run(pass *analysis.Pass) (interface{}, error) { }) errorFuncs := stringSet{} - insp.Preorder(funcs, func(node ast.Node) { + insp.Preorder(funcNodes, func(node ast.Node) { f := node.(*ast.FuncDecl) if isFuncReturningErr(f.Type, allTypes, errorTypes) { errorFuncs[f.Name.Name] = struct{}{} diff --git a/pkg/analyzer/facts.go b/pkg/analyzer/facts.go index 8711f9c..06f8d61 100644 --- a/pkg/analyzer/facts.go +++ b/pkg/analyzer/facts.go @@ -1,8 +1,10 @@ package analyzer import ( + "fmt" "go/ast" "go/token" + "go/types" "strings" "unicode" ) @@ -34,15 +36,19 @@ func isMethodError(f *ast.FuncDecl) (typeName string, ok bool) { if i, ok := v.X.(*ast.Ident); ok { return i.Name } + case *ast.IndexListExpr: + if i, ok := v.X.(*ast.Ident); ok { + return i.Name + } } - return "" + panic(fmt.Errorf("unsupported Error() receiver type %q", types.ExprString(e))) } switch rt := f.Recv.List[0].Type; v := rt.(type) { - case *ast.Ident, *ast.IndexExpr: // SomeError, SomeError[T] + case *ast.Ident, *ast.IndexExpr, *ast.IndexListExpr: // SomeError, SomeError[T], SomeError[T1, T2, ...] receiverType = unwrapIdentName(rt) - case *ast.StarExpr: // *SomeError, *SomeError[T] + case *ast.StarExpr: // *SomeError, *SomeError[T], *SomeError[T1, T2, ...] receiverType = unwrapIdentName(v.X) } diff --git a/pkg/analyzer/testdata/src/unusual/generics/issue24.go b/pkg/analyzer/testdata/src/unusual/generics/issue24.go new file mode 100644 index 0000000..48a4919 --- /dev/null +++ b/pkg/analyzer/testdata/src/unusual/generics/issue24.go @@ -0,0 +1,46 @@ +package generics + +import ( + "context" + "fmt" + "net/http" + "reflect" +) + +type ( + Req any + Resp any +) + +type timeoutErr[REQ Req, RESP Resp] struct { // want "the type name `timeoutErr` should conform to the `xxxError` format" + err error + sending bool +} + +func (e *timeoutErr[REQ, RESP]) Error() string { + var req REQ + var resp RESP + + direction := "sending" + if !e.sending { + direction = "receiving" + } + + return fmt.Sprintf("deferred call %T->%T timeout %s: %s", + reflect.TypeOf(req), reflect.TypeOf(resp), direction, e.err.Error()) +} + +func (e *timeoutErr[REQ, RESP]) Unwrap() error { + return e.err +} + +type TimeoutError[REQ Req, RESP Resp] struct{} // +func (TimeoutError[REQ, RESP]) Error() string { return "timeouted" } + +type ValErr[A, B, C, D, E, F any] struct{} // want "the type name `ValErr` should conform to the `XxxError` format" +func (ValErr[A, B, C, D, E, F]) Error() string { return "boom!" } + +var ( + ErrTimeout error = &timeoutErr[*http.Request, *http.Response]{err: context.DeadlineExceeded, sending: false} + tErr error = &timeoutErr[string, string]{err: context.DeadlineExceeded, sending: true} // want "the variable name `tErr` should conform to the `errXxx` format" +)