From 5738501abac29d061a954d76df641959e69e97b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D0=B5=D0=BB=D1=8B?= =?UTF-8?q?=D1=88=D0=B5=D0=B2?= Date: Thu, 26 May 2022 08:15:27 +0300 Subject: [PATCH] Support generics --- .golangci.yml | 7 +++ go.mod | 4 +- go.sum | 8 +-- pkg/analyzer/analyzer.go | 2 +- pkg/analyzer/analyzer_test.go | 1 + pkg/analyzer/facts.go | 53 ++++++++++++++----- .../src/unusual/generics/generic_types.go | 30 +++++++++++ 7 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 pkg/analyzer/testdata/src/unusual/generics/generic_types.go diff --git a/.golangci.yml b/.golangci.yml index 2def90d..195c587 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,6 +15,7 @@ linters: disable-all: true enable: - asciicheck + - bidichk - bodyclose - deadcode - depguard @@ -22,6 +23,9 @@ linters: - dupl - durationcheck - errcheck + - errchkjson + - errname + - execinquery - exhaustive - exportloopref - forbidigo @@ -51,9 +55,11 @@ linters: - misspell - nakedret - nilerr + - nilnil - nestif - noctx - nolintlint + - nosprintfhostport - prealloc - predeclared - revive @@ -63,6 +69,7 @@ linters: - structcheck - stylecheck - tagliatelle + - tenv - thelper - typecheck - unconvert diff --git a/go.mod b/go.mod index b355c16..4851edc 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module github.com/Antonboom/errname go 1.18 require ( - golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/tools v0.1.10 ) require ( golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect ) diff --git a/go.sum b/go.sum index 0609eb7..e5cb294 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM= -golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 984a6a9..6425db1 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -98,7 +98,7 @@ func run(pass *analysis.Pass) (interface{}, error) { ast.Inspect(f, inspectPkgLevelVarsOnly) } - return nil, nil + return nil, nil //nolint:nilnil } func reportAboutErrorType(pass *analysis.Pass, typePos token.Pos, typeName string, isArrayType bool) { diff --git a/pkg/analyzer/analyzer_test.go b/pkg/analyzer/analyzer_test.go index 651a477..b4028cd 100644 --- a/pkg/analyzer/analyzer_test.go +++ b/pkg/analyzer/analyzer_test.go @@ -10,6 +10,7 @@ func TestErrName(t *testing.T) { pkgs := []string{ "regular", "unusual/errortype", + "unusual/generics", "unusual/newfunc", } analysistest.Run(t, analysistest.TestData(), New(), pkgs...) diff --git a/pkg/analyzer/facts.go b/pkg/analyzer/facts.go index ce57159..8711f9c 100644 --- a/pkg/analyzer/facts.go +++ b/pkg/analyzer/facts.go @@ -8,10 +8,10 @@ import ( ) func isMethodError(f *ast.FuncDecl) (typeName string, ok bool) { - if f.Recv == nil { + if f.Recv == nil || len(f.Recv.List) != 1 { return "", false } - if f.Name.Name != "Error" { + if f.Name == nil || f.Name.Name != "Error" { return "", false } @@ -26,13 +26,24 @@ func isMethodError(f *ast.FuncDecl) (typeName string, ok bool) { var receiverType string - switch rt := f.Recv.List[0].Type.(type) { - case *ast.Ident: - receiverType = rt.Name - case *ast.StarExpr: - if i, ok := rt.X.(*ast.Ident); ok { - receiverType = i.Name + unwrapIdentName := func(e ast.Expr) string { + switch v := e.(type) { + case *ast.Ident: + return v.Name + case *ast.IndexExpr: + if i, ok := v.X.(*ast.Ident); ok { + return i.Name + } } + return "" + } + + switch rt := f.Recv.List[0].Type; v := rt.(type) { + case *ast.Ident, *ast.IndexExpr: // SomeError, SomeError[T] + receiverType = unwrapIdentName(rt) + + case *ast.StarExpr: // *SomeError, *SomeError[T] + receiverType = unwrapIdentName(v.X) } return receiverType, returnType.Name == "string" @@ -100,7 +111,7 @@ var knownErrConstructors = stringSet{ "errors.NewAssertionErrorWithWrappedErrf": {}, } -func isSentinelError( //nolint:gocognit +func isSentinelError( //nolint:gocognit,gocyclo v *ast.ValueSpec, pkgAliases map[string]string, allTypes, errorTypes, errorFuncs stringSet, @@ -151,6 +162,7 @@ func isSentinelError( //nolint:gocognit // var ErrEndOfFile = newErrEndOfFile() // var ErrEndOfFile = new(EndOfFileError) // const ErrEndOfFile = constError("end of file") + // var statusCodeError = new(SomePtrError[string]) case *ast.Ident: if isErrorType(fun.Name, allTypes, errorTypes) { return varName, true @@ -161,8 +173,13 @@ func isSentinelError( //nolint:gocognit } if fun.Name == "new" && len(vv.Args) == 1 { - if i, ok := vv.Args[0].(*ast.Ident); ok { + switch i := vv.Args[0].(type) { + case *ast.Ident: return varName, isErrorType(i.Name, allTypes, errorTypes) + case *ast.IndexExpr: + if ii, ok := i.X.(*ast.Ident); ok { + return varName, isErrorType(ii.Name, allTypes, errorTypes) + } } } @@ -172,19 +189,31 @@ func isSentinelError( //nolint:gocognit } // var ErrEndOfFile = &EndOfFileError{} + // var ErrOK = &SomePtrError[string]{Code: "200 OK"} case *ast.UnaryExpr: if vv.Op == token.AND { // & if lit, ok := vv.X.(*ast.CompositeLit); ok { - if i, ok := lit.Type.(*ast.Ident); ok { + switch i := lit.Type.(type) { + case *ast.Ident: return varName, isErrorType(i.Name, allTypes, errorTypes) + case *ast.IndexExpr: + if ii, ok := i.X.(*ast.Ident); ok { + return varName, isErrorType(ii.Name, allTypes, errorTypes) + } } } } // var ErrEndOfFile = EndOfFileError{} + // var ErrNotFound = SomeError[string]{Code: "Not Found"} case *ast.CompositeLit: - if i, ok := vv.Type.(*ast.Ident); ok { + switch i := vv.Type.(type) { + case *ast.Ident: return varName, isErrorType(i.Name, allTypes, errorTypes) + case *ast.IndexExpr: + if ii, ok := i.X.(*ast.Ident); ok { + return varName, isErrorType(ii.Name, allTypes, errorTypes) + } } } diff --git a/pkg/analyzer/testdata/src/unusual/generics/generic_types.go b/pkg/analyzer/testdata/src/unusual/generics/generic_types.go new file mode 100644 index 0000000..a9e4ce7 --- /dev/null +++ b/pkg/analyzer/testdata/src/unusual/generics/generic_types.go @@ -0,0 +1,30 @@ +package generics + +type NotErrorGeneric[T float64 | int] struct { + Limit T +} + +type SomeError[T ~string] struct{ Code T } + +func (e SomeError[T]) Error() string { return string(e.Code) } + +type SomePtrError[T ~string] struct{ Code T } + +func (e *SomePtrError[T]) Error() string { return string(e.Code) } + +type someErr[T ~string] struct{ Code T } // want "the type name `someErr` should conform to the `xxxError` format" +func (e someErr[T]) Error() string { return string(e.Code) } + +type SomePtrErr[T ~string] struct{ Code T } // want "the type name `SomePtrErr` should conform to the `XxxError` format" +func (e *SomePtrErr[T]) Error() string { return string(e.Code) } + +var ( + ErrOK = &SomePtrError[string]{Code: "200 OK"} + okErr = &SomePtrError[string]{Code: "200 OK"} // want "the variable name `okErr` should conform to the `errXxx` format" + + ErrNotFound = SomeError[string]{Code: "Not Found"} + NotFoundErr = SomeError[string]{Code: "Not Found"} // want "the variable name `NotFoundErr` should conform to the `ErrXxx` format" + + statusCodeError = new(SomePtrError[string]) // want "the variable name `statusCodeError` should conform to the `errXxx` format" + ExplicitError error = new(SomePtrError[string]) // want "the variable name `ExplicitError` should conform to the `ErrXxx` format" +)