From 2ed883018f242fd99ff327b47741ddcdec47972c Mon Sep 17 00:00:00 2001 From: Anton Telyshev Date: Sat, 5 Oct 2024 00:10:32 +0300 Subject: [PATCH] Go 1.23 (#46) * incredible refactoring based on linters writing experience * more tests for nested errors * Go 1.23 * upgrade deps --- .github/workflows/ci.yml | 4 +- .golangci.yml | 5 +- README.md | 234 +++++++++--------- Taskfile.yml | 6 +- go.mod | 6 +- go.sum | 9 +- pkg/analyzer/analyzer.go | 117 +++------ pkg/analyzer/analyzer_test.go | 16 -- pkg/analyzer/facts.go | 195 +-------------- pkg/analyzer/facts_test.go | 46 ---- .../testdata/src/regular/error_types.go | 13 +- .../src/regular/error_types_exceptions.go | 8 +- pkg/analyzer/testdata/src/regular/sentinel.go | 23 +- .../src/regular/sentinel_from_alias.go | 16 -- .../src/regular/sentinel_from_constants.go | 4 +- .../src/regular/sentinel_from_funcs.go | 34 +-- .../src/regular/sentinel_from_pkg_alias.go | 12 + .../src/regular/sentinel_from_types.go | 15 +- .../src/unusual/generics/generic_types.go | 12 +- .../testdata/src/unusual/generics/issue24.go | 6 +- .../src/unusual/newfunc/user_new_func.go | 2 +- 21 files changed, 260 insertions(+), 523 deletions(-) delete mode 100644 pkg/analyzer/testdata/src/regular/sentinel_from_alias.go create mode 100644 pkg/analyzer/testdata/src/regular/sentinel_from_pkg_alias.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23b2eea..4ed89ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,8 +8,8 @@ on: branches: [ master ] env: - GO_VERSION: ^1.22 - GOLANGCI_LINT_VERSION: v1.57.2 + GO_VERSION: ^1.23 + GOLANGCI_LINT_VERSION: v1.61.0 permissions: contents: read diff --git a/.golangci.yml b/.golangci.yml index 8e7ebf9..dc6cb0c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,15 +18,14 @@ linters: - asciicheck - bidichk - bodyclose + - copyloopvar - dogsled - dupl - durationcheck - errcheck - errchkjson - errname - - execinquery - exhaustive - - exportloopref - forbidigo - gci - gocheckcompilerdirectives @@ -40,7 +39,6 @@ linters: - gofumpt - goheader - goimports - - gomnd - gomoddirectives - gomodguard - goprintffuncname @@ -53,6 +51,7 @@ linters: - makezero - misspell - mirror + - mnd - nakedret - nilerr - nilnil diff --git a/README.md b/README.md index 737b33f..2b6f74f 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ At the time of writing this linter, I was unaware of **revive**.
```go // Bad. -type DecodeErr struct{} // the type name `DecodeErr` should conform to the `xxxError` format +type DecodeErr struct{} // the error type name `DecodeErr` should conform to the `xxxError` format func (d *DecodeErr) Error() string { /*...*/ } // Good. @@ -69,7 +69,7 @@ func (d *DecodeError) Error() string { /*...*/ } ```go // Bad. -var InvalidURLErr = errors.New("invalid url") // the variable name `InvalidURLErr` should conform to the `ErrXxx` format +var InvalidURLErr = errors.New("invalid url") // the sentinel error name `InvalidURLErr` should conform to the `ErrXxx` format // Good. var ErrInvalidURL = errors.New("invalid url") // or errInvalidURL @@ -166,108 +166,108 @@ func (ve ValidationErrors) Error() string { /*...*/ } ```go $ errname ./src/... -go/src/runtime/error.go:72:6: the type name `errorString` should conform to the `xxxError` format -go/src/runtime/error.go:80:6: the type name `errorAddressString` should conform to the `xxxError` format -go/src/runtime/panic.go:180:5: the variable name `shiftError` should conform to the `errXxx` format -go/src/runtime/panic.go:187:5: the variable name `divideError` should conform to the `errXxx` format -go/src/runtime/panic.go:194:5: the variable name `overflowError` should conform to the `errXxx` format -go/src/runtime/panic.go:201:5: the variable name `floatError` should conform to the `errXxx` format -go/src/runtime/panic.go:208:5: the variable name `memoryError` should conform to the `errXxx` format -go/src/errors/errors.go:63:6: the type name `errorString` should conform to the `xxxError` format -go/src/math/bits/bits_errors.go:12:5: the variable name `overflowError` should conform to the `errXxx` format -go/src/math/bits/bits_errors.go:15:5: the variable name `divideError` should conform to the `errXxx` format -go/src/syscall/syscall_unix.go:114:6: the type name `Errno` should conform to the `XxxError` format -go/src/time/format.go:394:5: the variable name `atoiError` should conform to the `errXxx` format -go/src/time/zoneinfo_read.go:110:5: the variable name `badData` should conform to the `errXxx` format -go/src/io/fs/walk.go:15:5: the variable name `SkipDir` should conform to the `ErrXxx` format -go/src/fmt/scan.go:465:5: the variable name `complexError` should conform to the `errXxx` format -go/src/fmt/scan.go:466:5: the variable name `boolError` should conform to the `errXxx` format -go/src/archive/tar/common.go:39:6: the type name `headerError` should conform to the `xxxError` format -go/src/context/context.go:157:5: the variable name `Canceled` should conform to the `ErrXxx` format -go/src/context/context.go:161:5: the variable name `DeadlineExceeded` should conform to the `ErrXxx` format -go/src/math/big/float.go:77:6: the type name `ErrNaN` should conform to the `XxxError` format -go/src/crypto/x509/internal/macos/security.go:39:6: the type name `OSStatus` should conform to the `XxxError` format -go/src/net/cgo_unix.go:34:6: the type name `addrinfoErrno` should conform to the `xxxError` format -go/src/crypto/x509/x509.go:875:6: the type name `UnhandledCriticalExtension` should conform to the `XxxError` format -go/src/crypto/x509/pem_decrypt.go:110:5: the variable name `IncorrectPasswordError` should conform to the `ErrXxx` format -go/src/crypto/x509/root.go:18:2: the variable name `systemRootsErr` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:18:2: the variable name `alertCloseNotify` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:19:2: the variable name `alertUnexpectedMessage` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:20:2: the variable name `alertBadRecordMAC` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:21:2: the variable name `alertDecryptionFailed` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:22:2: the variable name `alertRecordOverflow` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:23:2: the variable name `alertDecompressionFailure` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:24:2: the variable name `alertHandshakeFailure` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:25:2: the variable name `alertBadCertificate` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:26:2: the variable name `alertUnsupportedCertificate` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:27:2: the variable name `alertCertificateRevoked` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:28:2: the variable name `alertCertificateExpired` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:29:2: the variable name `alertCertificateUnknown` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:30:2: the variable name `alertIllegalParameter` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:31:2: the variable name `alertUnknownCA` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:32:2: the variable name `alertAccessDenied` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:33:2: the variable name `alertDecodeError` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:34:2: the variable name `alertDecryptError` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:35:2: the variable name `alertExportRestriction` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:36:2: the variable name `alertProtocolVersion` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:37:2: the variable name `alertInsufficientSecurity` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:38:2: the variable name `alertInternalError` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:39:2: the variable name `alertInappropriateFallback` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:40:2: the variable name `alertUserCanceled` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:41:2: the variable name `alertNoRenegotiation` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:42:2: the variable name `alertMissingExtension` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:43:2: the variable name `alertUnsupportedExtension` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:44:2: the variable name `alertCertificateUnobtainable` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:45:2: the variable name `alertUnrecognizedName` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:46:2: the variable name `alertBadCertificateStatusResponse` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:47:2: the variable name `alertBadCertificateHashValue` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:48:2: the variable name `alertUnknownPSKIdentity` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:49:2: the variable name `alertCertificateRequired` should conform to the `errXxx` format -go/src/crypto/tls/alert.go:50:2: the variable name `alertNoApplicationProtocol` should conform to the `errXxx` format -go/src/path/filepath/path.go:337:5: the variable name `SkipDir` should conform to the `ErrXxx` format -go/src/net/http/h2_bundle.go:1016:5: the variable name `http2errReadEmpty` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:1212:2: the variable name `http2errMixPseudoHeaderTypes` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:1213:2: the variable name `http2errPseudoAfterRegular` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:1712:5: the variable name `http2ErrFrameTooLarge` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:1866:2: the variable name `http2errStreamID` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:1867:2: the variable name `http2errDepStreamID` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:1868:2: the variable name `http2errPadLength` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:1869:2: the variable name `http2errPadBytes` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:3400:5: the variable name `http2errTimeout` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:3519:5: the variable name `http2errClosedPipeWrite` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:3629:2: the variable name `http2errClientDisconnected` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:3630:2: the variable name `http2errClosedBody` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:3631:2: the variable name `http2errHandlerComplete` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:3632:2: the variable name `http2errStreamClosed` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:4526:5: the variable name `http2errPrefaceTimeout` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:4746:5: the variable name `http2errHandlerPanicked` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:6287:2: the variable name `http2ErrRecursivePush` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:6288:2: the variable name `http2ErrPushLimitReached` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:6930:5: the variable name `http2ErrNoCachedConn` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:7016:2: the variable name `http2errClientConnClosed` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:7017:2: the variable name `http2errClientConnUnusable` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:7018:2: the variable name `http2errClientConnGotGoAway` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:7471:5: the variable name `http2errRequestCanceled` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:7803:2: the variable name `http2errStopReqBodyWrite` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:7806:2: the variable name `http2errStopReqBodyWriteAndCancel` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:7808:2: the variable name `http2errReqBodyTooLong` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:8667:5: the variable name `http2errClosedResponseBody` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:9021:2: the variable name `http2errResponseHeaderListSize` should conform to the `errXxx` format -go/src/net/http/h2_bundle.go:9022:2: the variable name `http2errRequestHeaderListSize` should conform to the `errXxx` format -go/src/go/scanner/errors.go:37:6: the type name `ErrorList` should conform to the `XxxError` format -go/src/html/template/template.go:34:5: the variable name `escapeOK` should conform to the `errXxx` format -go/src/image/png/reader.go:128:5: the variable name `chunkOrderError` should conform to the `errXxx` format -go/src/bufio/scan_test.go:308:5: the variable name `testError` should conform to the `errXxx` format -go/src/crypto/tls/handshake_client_test.go:1993:5: the variable name `brokenConnErr` should conform to the `errXxx` format -go/src/database/sql/sql_test.go:4281:5: the variable name `pingError` should conform to the `errXxx` format -go/src/errors/wrap_test.go:216:6: the type name `errorT` should conform to the `xxxError` format -go/src/errors/wrap_test.go:229:6: the type name `errorUncomparable` should conform to the `xxxError` format -go/src/fmt/errors_test.go:75:6: the type name `errString` should conform to the `xxxError` format -go/src/html/template/exec_test.go:233:5: the variable name `myError` should conform to the `errXxx` format -go/src/html/template/exec_test.go:1313:5: the variable name `alwaysError` should conform to the `errXxx` format -go/src/net/http/transport_test.go:6280:5: the variable name `timeoutProtoErr` should conform to the `errXxx` format -go/src/text/template/exec_test.go:229:5: the variable name `myError` should conform to the `errXxx` format -go/src/text/template/exec_test.go:1305:5: the variable name `alwaysError` should conform to the `errXxx` format +go/src/runtime/error.go:72:6: the error type name `errorString` should conform to the `xxxError` format +go/src/runtime/error.go:80:6: the error type name `errorAddressString` should conform to the `xxxError` format +go/src/runtime/panic.go:180:5: the sentinel error name `shiftError` should conform to the `errXxx` format +go/src/runtime/panic.go:187:5: the sentinel error name `divideError` should conform to the `errXxx` format +go/src/runtime/panic.go:194:5: the sentinel error name `overflowError` should conform to the `errXxx` format +go/src/runtime/panic.go:201:5: the sentinel error name `floatError` should conform to the `errXxx` format +go/src/runtime/panic.go:208:5: the sentinel error name `memoryError` should conform to the `errXxx` format +go/src/errors/errors.go:63:6: the error type name `errorString` should conform to the `xxxError` format +go/src/math/bits/bits_errors.go:12:5: the sentinel error name `overflowError` should conform to the `errXxx` format +go/src/math/bits/bits_errors.go:15:5: the sentinel error name `divideError` should conform to the `errXxx` format +go/src/syscall/syscall_unix.go:114:6: the error type name `Errno` should conform to the `XxxError` format +go/src/time/format.go:394:5: the sentinel error name `atoiError` should conform to the `errXxx` format +go/src/time/zoneinfo_read.go:110:5: the sentinel error name `badData` should conform to the `errXxx` format +go/src/io/fs/walk.go:15:5: the sentinel error name `SkipDir` should conform to the `ErrXxx` format +go/src/fmt/scan.go:465:5: the sentinel error name `complexError` should conform to the `errXxx` format +go/src/fmt/scan.go:466:5: the sentinel error name `boolError` should conform to the `errXxx` format +go/src/archive/tar/common.go:39:6: the error type name `headerError` should conform to the `xxxError` format +go/src/context/context.go:157:5: the sentinel error name `Canceled` should conform to the `ErrXxx` format +go/src/context/context.go:161:5: the sentinel error name `DeadlineExceeded` should conform to the `ErrXxx` format +go/src/math/big/float.go:77:6: the error type name `ErrNaN` should conform to the `XxxError` format +go/src/crypto/x509/internal/macos/security.go:39:6: the error type name `OSStatus` should conform to the `XxxError` format +go/src/net/cgo_unix.go:34:6: the error type name `addrinfoErrno` should conform to the `xxxError` format +go/src/crypto/x509/x509.go:875:6: the error type name `UnhandledCriticalExtension` should conform to the `XxxError` format +go/src/crypto/x509/pem_decrypt.go:110:5: the sentinel error name `IncorrectPasswordError` should conform to the `ErrXxx` format +go/src/crypto/x509/root.go:18:2: the sentinel error name `systemRootsErr` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:18:2: the sentinel error name `alertCloseNotify` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:19:2: the sentinel error name `alertUnexpectedMessage` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:20:2: the sentinel error name `alertBadRecordMAC` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:21:2: the sentinel error name `alertDecryptionFailed` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:22:2: the sentinel error name `alertRecordOverflow` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:23:2: the sentinel error name `alertDecompressionFailure` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:24:2: the sentinel error name `alertHandshakeFailure` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:25:2: the sentinel error name `alertBadCertificate` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:26:2: the sentinel error name `alertUnsupportedCertificate` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:27:2: the sentinel error name `alertCertificateRevoked` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:28:2: the sentinel error name `alertCertificateExpired` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:29:2: the sentinel error name `alertCertificateUnknown` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:30:2: the sentinel error name `alertIllegalParameter` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:31:2: the sentinel error name `alertUnknownCA` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:32:2: the sentinel error name `alertAccessDenied` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:33:2: the sentinel error name `alertDecodeError` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:34:2: the sentinel error name `alertDecryptError` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:35:2: the sentinel error name `alertExportRestriction` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:36:2: the sentinel error name `alertProtocolVersion` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:37:2: the sentinel error name `alertInsufficientSecurity` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:38:2: the sentinel error name `alertInternalError` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:39:2: the sentinel error name `alertInappropriateFallback` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:40:2: the sentinel error name `alertUserCanceled` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:41:2: the sentinel error name `alertNoRenegotiation` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:42:2: the sentinel error name `alertMissingExtension` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:43:2: the sentinel error name `alertUnsupportedExtension` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:44:2: the sentinel error name `alertCertificateUnobtainable` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:45:2: the sentinel error name `alertUnrecognizedName` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:46:2: the sentinel error name `alertBadCertificateStatusResponse` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:47:2: the sentinel error name `alertBadCertificateHashValue` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:48:2: the sentinel error name `alertUnknownPSKIdentity` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:49:2: the sentinel error name `alertCertificateRequired` should conform to the `errXxx` format +go/src/crypto/tls/alert.go:50:2: the sentinel error name `alertNoApplicationProtocol` should conform to the `errXxx` format +go/src/path/filepath/path.go:337:5: the sentinel error name `SkipDir` should conform to the `ErrXxx` format +go/src/net/http/h2_bundle.go:1016:5: the sentinel error name `http2errReadEmpty` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:1212:2: the sentinel error name `http2errMixPseudoHeaderTypes` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:1213:2: the sentinel error name `http2errPseudoAfterRegular` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:1712:5: the sentinel error name `http2ErrFrameTooLarge` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:1866:2: the sentinel error name `http2errStreamID` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:1867:2: the sentinel error name `http2errDepStreamID` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:1868:2: the sentinel error name `http2errPadLength` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:1869:2: the sentinel error name `http2errPadBytes` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:3400:5: the sentinel error name `http2errTimeout` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:3519:5: the sentinel error name `http2errClosedPipeWrite` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:3629:2: the sentinel error name `http2errClientDisconnected` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:3630:2: the sentinel error name `http2errClosedBody` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:3631:2: the sentinel error name `http2errHandlerComplete` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:3632:2: the sentinel error name `http2errStreamClosed` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:4526:5: the sentinel error name `http2errPrefaceTimeout` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:4746:5: the sentinel error name `http2errHandlerPanicked` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:6287:2: the sentinel error name `http2ErrRecursivePush` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:6288:2: the sentinel error name `http2ErrPushLimitReached` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:6930:5: the sentinel error name `http2ErrNoCachedConn` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:7016:2: the sentinel error name `http2errClientConnClosed` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:7017:2: the sentinel error name `http2errClientConnUnusable` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:7018:2: the sentinel error name `http2errClientConnGotGoAway` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:7471:5: the sentinel error name `http2errRequestCanceled` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:7803:2: the sentinel error name `http2errStopReqBodyWrite` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:7806:2: the sentinel error name `http2errStopReqBodyWriteAndCancel` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:7808:2: the sentinel error name `http2errReqBodyTooLong` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:8667:5: the sentinel error name `http2errClosedResponseBody` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:9021:2: the sentinel error name `http2errResponseHeaderListSize` should conform to the `errXxx` format +go/src/net/http/h2_bundle.go:9022:2: the sentinel error name `http2errRequestHeaderListSize` should conform to the `errXxx` format +go/src/go/scanner/errors.go:37:6: the error type name `ErrorList` should conform to the `XxxError` format +go/src/html/template/template.go:34:5: the sentinel error name `escapeOK` should conform to the `errXxx` format +go/src/image/png/reader.go:128:5: the sentinel error name `chunkOrderError` should conform to the `errXxx` format +go/src/bufio/scan_test.go:308:5: the sentinel error name `testError` should conform to the `errXxx` format +go/src/crypto/tls/handshake_client_test.go:1993:5: the sentinel error name `brokenConnErr` should conform to the `errXxx` format +go/src/database/sql/sql_test.go:4281:5: the sentinel error name `pingError` should conform to the `errXxx` format +go/src/errors/wrap_test.go:216:6: the error type name `errorT` should conform to the `xxxError` format +go/src/errors/wrap_test.go:229:6: the error type name `errorUncomparable` should conform to the `xxxError` format +go/src/fmt/errors_test.go:75:6: the error type name `errString` should conform to the `xxxError` format +go/src/html/template/exec_test.go:233:5: the sentinel error name `myError` should conform to the `errXxx` format +go/src/html/template/exec_test.go:1313:5: the sentinel error name `alwaysError` should conform to the `errXxx` format +go/src/net/http/transport_test.go:6280:5: the sentinel error name `timeoutProtoErr` should conform to the `errXxx` format +go/src/text/template/exec_test.go:229:5: the sentinel error name `myError` should conform to the `errXxx` format +go/src/text/template/exec_test.go:1305:5: the sentinel error name `alwaysError` should conform to the `errXxx` format ``` @@ -287,19 +287,19 @@ $ errname./... ```go $ errname./... -terraform/internal/getmodules/file_detector.go:59:6: the type name `MaybeRelativePathErr` should conform to the `XxxError` format -terraform/internal/getproviders/errors.go:13:6: the type name `ErrHostNoProviders` should conform to the `XxxError` format -terraform/internal/getproviders/errors.go:39:6: the type name `ErrHostUnreachable` should conform to the `XxxError` format -terraform/internal/getproviders/errors.go:57:6: the type name `ErrUnauthorized` should conform to the `XxxError` format -terraform/internal/getproviders/errors.go:80:6: the type name `ErrProviderNotFound` should conform to the `XxxError` format -terraform/internal/getproviders/errors.go:104:6: the type name `ErrRegistryProviderNotKnown` should conform to the `XxxError` format -terraform/internal/getproviders/errors.go:123:6: the type name `ErrPlatformNotSupported` should conform to the `XxxError` format -terraform/internal/getproviders/errors.go:159:6: the type name `ErrProtocolNotSupported` should conform to the `XxxError` format -terraform/internal/getproviders/errors.go:181:6: the type name `ErrQueryFailed` should conform to the `XxxError` format -terraform/internal/getproviders/errors.go:219:6: the type name `ErrRequestCanceled` should conform to the `XxxError` format -terraform/internal/registry/errors.go:10:6: the type name `errModuleNotFound` should conform to the `xxxError` format -terraform/internal/backend/remote-state/consul/client.go:36:5: the variable name `lostLockErr` should conform to the `errXxx` format -terraform/internal/command/cliconfig/credentials.go:408:6: the type name `ErrUnwritableHostCredentials` should conform to the `XxxError` format +terraform/internal/getmodules/file_detector.go:59:6: the error type name `MaybeRelativePathErr` should conform to the `XxxError` format +terraform/internal/getproviders/errors.go:13:6: the error type name `ErrHostNoProviders` should conform to the `XxxError` format +terraform/internal/getproviders/errors.go:39:6: the error type name `ErrHostUnreachable` should conform to the `XxxError` format +terraform/internal/getproviders/errors.go:57:6: the error type name `ErrUnauthorized` should conform to the `XxxError` format +terraform/internal/getproviders/errors.go:80:6: the error type name `ErrProviderNotFound` should conform to the `XxxError` format +terraform/internal/getproviders/errors.go:104:6: the error type name `ErrRegistryProviderNotKnown` should conform to the `XxxError` format +terraform/internal/getproviders/errors.go:123:6: the error type name `ErrPlatformNotSupported` should conform to the `XxxError` format +terraform/internal/getproviders/errors.go:159:6: the error type name `ErrProtocolNotSupported` should conform to the `XxxError` format +terraform/internal/getproviders/errors.go:181:6: the error type name `ErrQueryFailed` should conform to the `XxxError` format +terraform/internal/getproviders/errors.go:219:6: the error type name `ErrRequestCanceled` should conform to the `XxxError` format +terraform/internal/registry/errors.go:10:6: the error type name `errModuleNotFound` should conform to the `xxxError` format +terraform/internal/backend/remote-state/consul/client.go:36:5: the sentinel error name `lostLockErr` should conform to the `errXxx` format +terraform/internal/command/cliconfig/credentials.go:408:6: the error type name `ErrUnwritableHostCredentials` should conform to the `XxxError` format ``` diff --git a/Taskfile.yml b/Taskfile.yml index bbdcb5c..196986e 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -10,7 +10,7 @@ tasks: - task: tidy - task: fmt - task: lint - - task: tests + - task: test - task: install tools:install: @@ -34,12 +34,12 @@ tasks: - echo "Lint..." - golangci-lint run --fix ./... - tests: + test: cmds: - echo "Tests..." - go test ./... - tests:coverage: + test:coverage: cmds: - echo "Tests with coverage..." - go test -coverprofile=coverage.out ./... diff --git a/go.mod b/go.mod index 1ff2625..487b479 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/Antonboom/errname -go 1.20 +go 1.22.1 -require golang.org/x/tools v0.24.0 +require golang.org/x/tools v0.26.0 require ( - golang.org/x/mod v0.20.0 // indirect + golang.org/x/mod v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect ) diff --git a/go.sum b/go.sum index cce3a37..486c8d4 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index aa85225..60f3aea 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -1,11 +1,9 @@ package analyzer import ( - "fmt" "go/ast" "go/token" - "strconv" - "strings" + "go/types" "unicode" "golang.org/x/tools/go/analysis" @@ -23,81 +21,56 @@ func New() *analysis.Analyzer { } } -type stringSet = map[string]struct{} - -var ( - 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(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 { - pkgAliases[n.Name] = getPkgFromPath(path) - } - } - }) - - allTypes := stringSet{} - typesSpecs := map[string]*ast.TypeSpec{} - 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(funcNodes, func(node ast.Node) { - f := node.(*ast.FuncDecl) - t, ok := isMethodError(f) - if !ok { - return - } - errorTypes[t] = struct{}{} - - tSpec, ok := typesSpecs[t] - if !ok { - panic(fmt.Sprintf("no specification for type %q", t)) - } - - if _, ok := tSpec.Type.(*ast.ArrayType); ok { - if !isValidErrorArrayTypeName(t) { - reportAboutErrorType(pass, tSpec.Pos(), t, true) - } - } else if !isValidErrorTypeName(t) { - reportAboutErrorType(pass, tSpec.Pos(), t, false) + insp.Nodes([]ast.Node{ + (*ast.TypeSpec)(nil), + (*ast.ValueSpec)(nil), + (*ast.FuncDecl)(nil), + }, func(node ast.Node, push bool) bool { + if !push { + return false } - }) - errorFuncs := stringSet{} - insp.Preorder(funcNodes, func(node ast.Node) { - f := node.(*ast.FuncDecl) - if isFuncReturningErr(f.Type, allTypes, errorTypes) { - errorFuncs[f.Name.Name] = struct{}{} - } - }) - - inspectPkgLevelVarsOnly := func(node ast.Node) bool { switch v := node.(type) { case *ast.FuncDecl: return false case *ast.ValueSpec: - if name, ok := isSentinelError(v, pkgAliases, allTypes, errorTypes, errorFuncs); ok && !isValidErrorVarName(name) { - reportAboutErrorVar(pass, v.Pos(), name) + if len(v.Names) != 1 { + return false + } + ident := v.Names[0] + + if exprImplementsError(pass, ident) && !isValidErrorVarName(ident.Name) { + reportAboutSentinelError(pass, v.Pos(), ident.Name) + } + return false + + case *ast.TypeSpec: + tt := pass.TypesInfo.TypeOf(v.Name) + if tt == nil { + return false + } + // NOTE(a.telyshev): Pointer is the hack against Error() method with pointer receiver. + if !typeImplementsError(types.NewPointer(tt)) { + return false + } + + name := v.Name.Name + if _, ok := v.Type.(*ast.ArrayType); ok { + if !isValidErrorArrayTypeName(name) { + reportAboutErrorType(pass, v.Pos(), name, true) + } + } else if !isValidErrorTypeName(name) { + reportAboutErrorType(pass, v.Pos(), name, false) } + return false } + return true - } - for _, f := range pass.Files { - ast.Inspect(f, inspectPkgLevelVarsOnly) - } + }) return nil, nil //nolint:nilnil } @@ -113,23 +86,15 @@ func reportAboutErrorType(pass *analysis.Pass, typePos token.Pos, typeName strin if isArrayType { form += "s" } - pass.Reportf(typePos, "the type name `%s` should conform to the `%s` format", typeName, form) + pass.Reportf(typePos, "the error type name `%s` should conform to the `%s` format", typeName, form) } -func reportAboutErrorVar(pass *analysis.Pass, pos token.Pos, varName string) { +func reportAboutSentinelError(pass *analysis.Pass, pos token.Pos, varName string) { var form string if unicode.IsLower([]rune(varName)[0]) { form = "errXxx" } else { form = "ErrXxx" } - pass.Reportf(pos, "the variable name `%s` should conform to the `%s` format", varName, form) -} - -func getPkgFromPath(p string) string { - idx := strings.LastIndex(p, "/") - if idx == -1 { - return p - } - return p[idx+1:] + pass.Reportf(pos, "the sentinel error name `%s` should conform to the `%s` format", varName, form) } diff --git a/pkg/analyzer/analyzer_test.go b/pkg/analyzer/analyzer_test.go index b4028cd..e905f5c 100644 --- a/pkg/analyzer/analyzer_test.go +++ b/pkg/analyzer/analyzer_test.go @@ -15,19 +15,3 @@ func TestErrName(t *testing.T) { } analysistest.Run(t, analysistest.TestData(), New(), pkgs...) } - -func Test_getPkgFromPath(t *testing.T) { - for _, tt := range []struct { - in, expected string - }{ - {}, - {in: "errors", expected: "errors"}, - {in: "github/pkg/errors", expected: "errors"}, - {in: "github.com/cockroachdb/errors", expected: "errors"}, - } { - pkg := getPkgFromPath(tt.in) - if pkg != tt.expected { - t.Errorf("%q != %q", pkg, tt.expected) - } - } -} diff --git a/pkg/analyzer/facts.go b/pkg/analyzer/facts.go index 06f8d61..f02ed55 100644 --- a/pkg/analyzer/facts.go +++ b/pkg/analyzer/facts.go @@ -1,58 +1,22 @@ package analyzer import ( - "fmt" "go/ast" - "go/token" "go/types" "strings" "unicode" -) - -func isMethodError(f *ast.FuncDecl) (typeName string, ok bool) { - if f.Recv == nil || len(f.Recv.List) != 1 { - return "", false - } - if f.Name == nil || f.Name.Name != "Error" { - return "", false - } - if f.Type == nil || f.Type.Results == nil || len(f.Type.Results.List) != 1 { - return "", false - } - - returnType, ok := f.Type.Results.List[0].Type.(*ast.Ident) - if !ok { - return "", false - } - - var receiverType string - - 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 - } - case *ast.IndexListExpr: - if i, ok := v.X.(*ast.Ident); ok { - return i.Name - } - } - panic(fmt.Errorf("unsupported Error() receiver type %q", types.ExprString(e))) - } + "golang.org/x/tools/go/analysis" +) - switch rt := f.Recv.List[0].Type; v := rt.(type) { - case *ast.Ident, *ast.IndexExpr, *ast.IndexListExpr: // SomeError, SomeError[T], SomeError[T1, T2, ...] - receiverType = unwrapIdentName(rt) +var errorIface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) - case *ast.StarExpr: // *SomeError, *SomeError[T], *SomeError[T1, T2, ...] - receiverType = unwrapIdentName(v.X) - } +func exprImplementsError(pass *analysis.Pass, e ast.Expr) bool { + return typeImplementsError(pass.TypesInfo.TypeOf(e)) +} - return receiverType, returnType.Name == "string" +func typeImplementsError(t types.Type) bool { + return t != nil && types.Implements(t, errorIface) } func isValidErrorTypeName(s string) bool { @@ -83,149 +47,6 @@ func isValidErrorArrayTypeName(s string) bool { return words[len(words)-1] == "errors" } -func isFuncReturningErr(fType *ast.FuncType, allTypes, errorTypes stringSet) bool { - if fType == nil || fType.Results == nil || len(fType.Results.List) != 1 { - return false - } - - var returnTypeName string - switch rt := fType.Results.List[0].Type.(type) { - case *ast.Ident: - returnTypeName = rt.Name - case *ast.StarExpr: - if i, ok := rt.X.(*ast.Ident); ok { - returnTypeName = i.Name - } - } - - return isErrorType(returnTypeName, allTypes, errorTypes) -} - -func isErrorType(tName string, allTypes, errorTypes stringSet) bool { - _, isUserType := allTypes[tName] - _, isErrType := errorTypes[tName] - return isErrType || (tName == "error" && !isUserType) -} - -var knownErrConstructors = stringSet{ - "fmt.Errorf": {}, - "errors.Errorf": {}, - "errors.New": {}, - "errors.Newf": {}, - "errors.NewWithDepth": {}, - "errors.NewWithDepthf": {}, - "errors.NewAssertionErrorWithWrappedErrf": {}, -} - -func isSentinelError( //nolint:gocognit,gocyclo - v *ast.ValueSpec, - pkgAliases map[string]string, - allTypes, errorTypes, errorFuncs stringSet, -) (varName string, ok bool) { - if len(v.Names) != 1 { - return "", false - } - varName = v.Names[0].Name - - switch vv := v.Type.(type) { - // var ErrEndOfFile error - // var ErrEndOfFile SomeErrType - case *ast.Ident: - if isErrorType(vv.Name, allTypes, errorTypes) { - return varName, true - } - - // var ErrEndOfFile *SomeErrType - case *ast.StarExpr: - if i, ok := vv.X.(*ast.Ident); ok && isErrorType(i.Name, allTypes, errorTypes) { - return varName, true - } - } - - if len(v.Values) != 1 { - return "", false - } - - switch vv := v.Values[0].(type) { - case *ast.CallExpr: - switch fun := vv.Fun.(type) { - // var ErrEndOfFile = errors.New("end of file") - case *ast.SelectorExpr: - pkg, ok := fun.X.(*ast.Ident) - if !ok { - return "", false - } - pkgFun := fun.Sel - - pkgName := pkg.Name - if a, ok := pkgAliases[pkgName]; ok { - pkgName = a - } - - _, ok = knownErrConstructors[pkgName+"."+pkgFun.Name] - return varName, ok - - // 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 - } - - if _, ok := errorFuncs[fun.Name]; ok { - return varName, true - } - - if fun.Name == "new" && len(vv.Args) == 1 { - 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) - } - } - } - - // var ErrEndOfFile = func() error { ... } - case *ast.FuncLit: - return varName, isFuncReturningErr(fun.Type, allTypes, errorTypes) - } - - // 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 { - 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: - 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) - } - } - } - - return "", false -} - func isValidErrorVarName(s string) bool { if isInitialism(s) { return true diff --git a/pkg/analyzer/facts_test.go b/pkg/analyzer/facts_test.go index 268fff1..af765f8 100644 --- a/pkg/analyzer/facts_test.go +++ b/pkg/analyzer/facts_test.go @@ -571,52 +571,6 @@ func Test_isValidErrorVarName_invalidVars(t *testing.T) { } } -func Test_isErrorType(t *testing.T) { - t.Run("custom error type", func(t *testing.T) { - b := isErrorType( - "AddrError", - stringSet{"AddrError": {}, "User": {}}, - stringSet{"AddrError": {}}, - ) - if !b { - t.Fail() - } - }) - - t.Run("builtin error type", func(t *testing.T) { - b := isErrorType( - "error", - stringSet{"AddrError": {}, "User": {}}, - stringSet{"AddrError": {}}, - ) - if !b { - t.Fail() - } - }) - - t.Run("collided error type (without Error method)", func(t *testing.T) { - b := isErrorType( - "error", - stringSet{"AddrError": {}, "User": {}, "error": {}}, - stringSet{"AddrError": {}}, - ) - if b { - t.Fail() - } - }) - - t.Run("collided error type (with Error method)", func(t *testing.T) { - b := isErrorType( - "error", - stringSet{"AddrError": {}, "User": {}, "error": {}}, - stringSet{"AddrError": {}, "error": {}}, - ) - if !b { - t.Fail() - } - }) -} - func Test_isInitialism(t *testing.T) { for _, tt := range []struct { in string diff --git a/pkg/analyzer/testdata/src/regular/error_types.go b/pkg/analyzer/testdata/src/regular/error_types.go index a68ffff..e596c65 100644 --- a/pkg/analyzer/testdata/src/regular/error_types.go +++ b/pkg/analyzer/testdata/src/regular/error_types.go @@ -9,14 +9,19 @@ type DNSConfigError struct{} func (D DNSConfigError) Error() string { return "DNS config error" } -type someTypeWithoutPtr struct{} // want "the type name `someTypeWithoutPtr` should conform to the `xxxError` format" +type someTypeWithoutPtr struct{} // want "the error type name `someTypeWithoutPtr` should conform to the `xxxError` format" func (s someTypeWithoutPtr) Error() string { return "someTypeWithoutPtr" } -type SomeTypeWithoutPtr struct{} // want "the type name `SomeTypeWithoutPtr` should conform to the `XxxError` format" +type SomeTypeWithoutPtr struct{} // want "the error type name `SomeTypeWithoutPtr` should conform to the `XxxError` format" func (s SomeTypeWithoutPtr) Error() string { return "SomeTypeWithoutPtr" } -type someTypeWithPtr struct{} // want "the type name `someTypeWithPtr` should conform to the `xxxError` format" +type someTypeWithPtr struct{} // want "the error type name `someTypeWithPtr` should conform to the `xxxError` format" func (s *someTypeWithPtr) Error() string { return "someTypeWithPtr" } -type SomeTypeWithPtr struct{} // want "the type name `SomeTypeWithPtr` should conform to the `XxxError` format" +type ( + SomeTypeAlias = SomeTypeWithPtr // want "the error type name `SomeTypeAlias` should conform to the `XxxError` format" + + SomeTypeWithPtr struct{} // want "the error type name `SomeTypeWithPtr` should conform to the `XxxError` format" +) + func (s *SomeTypeWithPtr) Error() string { return "SomeTypeWithPtr" } diff --git a/pkg/analyzer/testdata/src/regular/error_types_exceptions.go b/pkg/analyzer/testdata/src/regular/error_types_exceptions.go index 8f6b2cf..208df8e 100644 --- a/pkg/analyzer/testdata/src/regular/error_types_exceptions.go +++ b/pkg/analyzer/testdata/src/regular/error_types_exceptions.go @@ -18,14 +18,14 @@ type tenErrors [10]string func (te tenErrors) Error() string { return strings.Join(te[:], "\n") } -type MultiError []error // want "the type name `MultiError` should conform to the `XxxErrors` format" +type MultiError []error // want "the error type name `MultiError` should conform to the `XxxErrors` format" func (me MultiError) Error() string { return "" } -type multiError []error // want "the type name `multiError` should conform to the `xxxErrors` format" +type multiError []error // want "the error type name `multiError` should conform to the `xxxErrors` format" func (me multiError) Error() string { return "" } -type TwoError [2]error // want "the type name `TwoError` should conform to the `XxxErrors` format" +type TwoError [2]error // want "the error type name `TwoError` should conform to the `XxxErrors` format" func (te TwoError) Error() string { return te[0].Error() + "\n" + te[1].Error() } -type twoError [2]error // want "the type name `twoError` should conform to the `xxxErrors` format" +type twoError [2]error // want "the error type name `twoError` should conform to the `xxxErrors` format" func (te twoError) Error() string { return te[0].Error() + "\n" + te[1].Error() } diff --git a/pkg/analyzer/testdata/src/regular/sentinel.go b/pkg/analyzer/testdata/src/regular/sentinel.go index 32ee9f2..cd5d3f5 100644 --- a/pkg/analyzer/testdata/src/regular/sentinel.go +++ b/pkg/analyzer/testdata/src/regular/sentinel.go @@ -10,11 +10,11 @@ var ( ErrEndOfFile = errors.New("end of file") errEndOfFile = errors.New("end of file") - EndOfFileError = errors.New("end of file") // want "the variable name `EndOfFileError` should conform to the `ErrXxx` format" - ErrorEndOfFile = errors.New("end of file") // want "the variable name `ErrorEndOfFile` should conform to the `ErrXxx` format" - EndOfFileErr = errors.New("end of file") // want "the variable name `EndOfFileErr` should conform to the `ErrXxx` format" - endOfFileError = errors.New("end of file") // want "the variable name `endOfFileError` should conform to the `errXxx` format" - errorEndOfFile = errors.New("end of file") // want "the variable name `errorEndOfFile` should conform to the `errXxx` format" + EndOfFileError = errors.New("end of file") // want "the sentinel error name `EndOfFileError` should conform to the `ErrXxx` format" + ErrorEndOfFile = errors.New("end of file") // want "the sentinel error name `ErrorEndOfFile` should conform to the `ErrXxx` format" + EndOfFileErr = errors.New("end of file") // want "the sentinel error name `EndOfFileErr` should conform to the `ErrXxx` format" + endOfFileError = errors.New("end of file") // want "the sentinel error name `endOfFileError` should conform to the `errXxx` format" + errorEndOfFile = errors.New("end of file") // want "the sentinel error name `errorEndOfFile` should conform to the `errXxx` format" ) const maxSize = 256 @@ -23,11 +23,20 @@ var ( ErrOutOfSize = fmt.Errorf("out of size (max %d)", maxSize) errOutOfSize = fmt.Errorf("out of size (max %d)", maxSize) - OutOfSizeError = fmt.Errorf("out of size (max %d)", maxSize) // want "the variable name `OutOfSizeError` should conform to the `ErrXxx` format" - outOfSizeError = fmt.Errorf("out of size (max %d)", maxSize) // want "the variable name `outOfSizeError` should conform to the `errXxx` format" + OutOfSizeError = fmt.Errorf("out of size (max %d)", maxSize) // want "the sentinel error name `OutOfSizeError` should conform to the `ErrXxx` format" + outOfSizeError = fmt.Errorf("out of size (max %d)", maxSize) // want "the sentinel error name `outOfSizeError` should conform to the `errXxx` format" ) func errInsideFuncIsNotSentinel() error { var lastErr error return lastErr } + +var _ = func() { + run("", func() { + var orderErr error + _ = orderErr + }) +} + +func run(_ string, _ func()) {} diff --git a/pkg/analyzer/testdata/src/regular/sentinel_from_alias.go b/pkg/analyzer/testdata/src/regular/sentinel_from_alias.go deleted file mode 100644 index b18ea8a..0000000 --- a/pkg/analyzer/testdata/src/regular/sentinel_from_alias.go +++ /dev/null @@ -1,16 +0,0 @@ -package regular - -import ( - stderrors "errors" - stdfmt "fmt" -) - -var ( - ErrAlias = stderrors.New("err from alias") - AliasErr = stderrors.New("err from alias") // want "the variable name `AliasErr` should conform to the `ErrXxx` format" -) - -var ( - ErrOutOfSize2 = stdfmt.Errorf("out of size (max %d)", maxSize) - OutOfSizeError2 = stdfmt.Errorf("out of size (max %d)", maxSize) // want "the variable name `OutOfSizeError2` should conform to the `ErrXxx` format" -) diff --git a/pkg/analyzer/testdata/src/regular/sentinel_from_constants.go b/pkg/analyzer/testdata/src/regular/sentinel_from_constants.go index 5c75760..b67cf71 100644 --- a/pkg/analyzer/testdata/src/regular/sentinel_from_constants.go +++ b/pkg/analyzer/testdata/src/regular/sentinel_from_constants.go @@ -9,6 +9,6 @@ func (e constError) Error() string { const ( ErrTooManyErrors constError = "too many errors found" - ErrorTooMany1 constError = "too many errors found" // want "the variable name `ErrorTooMany1` should conform to the `ErrXxx` format" - ErrorTooMany2 = constError("too many errors found") // want "the variable name `ErrorTooMany2` should conform to the `ErrXxx` format" + ErrorTooMany1 constError = "too many errors found" // want "the sentinel error name `ErrorTooMany1` should conform to the `ErrXxx` format" + ErrorTooMany2 = constError("too many errors found") // want "the sentinel error name `ErrorTooMany2` should conform to the `ErrXxx` format" ) diff --git a/pkg/analyzer/testdata/src/regular/sentinel_from_funcs.go b/pkg/analyzer/testdata/src/regular/sentinel_from_funcs.go index 8777853..3c295fd 100644 --- a/pkg/analyzer/testdata/src/regular/sentinel_from_funcs.go +++ b/pkg/analyzer/testdata/src/regular/sentinel_from_funcs.go @@ -11,28 +11,28 @@ var ( ErrF = &SomeTypeWithPtr{} ErrG = SomeTypeWithoutPtr{} - AErr = newSomeTypeWithPtr() // want "the variable name `AErr` should conform to the `ErrXxx` format" - BErr = newSomeTypeWithPtr2() // want "the variable name `BErr` should conform to the `ErrXxx` format" - CErr = newSomeTypeWithoutPtr() // want "the variable name `CErr` should conform to the `ErrXxx` format" - DErr = newSomeTypeWithoutPtr2() // want "the variable name `DErr` should conform to the `ErrXxx` format" - EErr = new(SomeTypeWithPtr) // want "the variable name `EErr` should conform to the `ErrXxx` format" - FErr = &SomeTypeWithPtr{} // want "the variable name `FErr` should conform to the `ErrXxx` format" - GErr = SomeTypeWithoutPtr{} // want "the variable name `GErr` should conform to the `ErrXxx` format" - - AErrr error = newSomeTypeWithPtr2() // want "the variable name `AErrr` should conform to the `ErrXxx` format" - BErrr error = newSomeTypeWithoutPtr() // want "the variable name `BErrr` should conform to the `ErrXxx` format" - CErrr error = new(SomeTypeWithPtr) // want "the variable name `CErrr` should conform to the `ErrXxx` format" - DErrr error = &SomeTypeWithPtr{} // want "the variable name `DErrr` should conform to the `ErrXxx` format" - EErrr error = SomeTypeWithoutPtr{} // want "the variable name `EErrr` should conform to the `ErrXxx` format" + AErr = newSomeTypeWithPtr() // want "the sentinel error name `AErr` should conform to the `ErrXxx` format" + BErr = newSomeTypeWithPtr2() // want "the sentinel error name `BErr` should conform to the `ErrXxx` format" + CErr = newSomeTypeWithoutPtr() // want "the sentinel error name `CErr` should conform to the `ErrXxx` format" + DErr = newSomeTypeWithoutPtr2() // want "the sentinel error name `DErr` should conform to the `ErrXxx` format" + EErr = new(SomeTypeWithPtr) // want "the sentinel error name `EErr` should conform to the `ErrXxx` format" + FErr = &SomeTypeWithPtr{} // want "the sentinel error name `FErr` should conform to the `ErrXxx` format" + GErr = SomeTypeWithoutPtr{} // want "the sentinel error name `GErr` should conform to the `ErrXxx` format" + + AErrr error = newSomeTypeWithPtr2() // want "the sentinel error name `AErrr` should conform to the `ErrXxx` format" + BErrr error = newSomeTypeWithoutPtr() // want "the sentinel error name `BErrr` should conform to the `ErrXxx` format" + CErrr error = new(SomeTypeWithPtr) // want "the sentinel error name `CErrr` should conform to the `ErrXxx` format" + DErrr error = &SomeTypeWithPtr{} // want "the sentinel error name `DErrr` should conform to the `ErrXxx` format" + EErrr error = SomeTypeWithoutPtr{} // want "the sentinel error name `EErrr` should conform to the `ErrXxx` format" ErrByAnonymousFunc = func() error { return nil } - ByAnonymousFuncErr = func() error { return io.EOF }() // want "the variable name `ByAnonymousFuncErr` should conform to the `ErrXxx` format" + ByAnonymousFuncErr = func() error { return io.EOF }() // want "the sentinel error name `ByAnonymousFuncErr` should conform to the `ErrXxx` format" ) var ( - InitializedLaterError error // want "the variable name `InitializedLaterError` should conform to the `ErrXxx` format" - InitializedLaterImplicitError SomeTypeWithoutPtr // want "the variable name `InitializedLaterImplicitError` should conform to the `ErrXxx` format" - InitializedLaterImplicitPtrError *SomeTypeWithPtr // want "the variable name `InitializedLaterImplicitPtrError` should conform to the `ErrXxx` format" + InitializedLaterError error // want "the sentinel error name `InitializedLaterError` should conform to the `ErrXxx` format" + InitializedLaterImplicitError SomeTypeWithoutPtr // want "the sentinel error name `InitializedLaterImplicitError` should conform to the `ErrXxx` format" + InitializedLaterImplicitPtrError *SomeTypeWithPtr // want "the sentinel error name `InitializedLaterImplicitPtrError` should conform to the `ErrXxx` format" ) func init() { diff --git a/pkg/analyzer/testdata/src/regular/sentinel_from_pkg_alias.go b/pkg/analyzer/testdata/src/regular/sentinel_from_pkg_alias.go new file mode 100644 index 0000000..f832547 --- /dev/null +++ b/pkg/analyzer/testdata/src/regular/sentinel_from_pkg_alias.go @@ -0,0 +1,12 @@ +package regular + +import ( + stderrors "errors" + stdfmt "fmt" +) + +var ErrAlias = stderrors.New("err from alias") +var AliasErr = stderrors.New("err from alias") // want "the sentinel error name `AliasErr` should conform to the `ErrXxx` format" + +var ErrOutOfSize2 = stdfmt.Errorf("out of size (max %d)", maxSize) +var OutOfSizeError2 = stdfmt.Errorf("out of size (max %d)", maxSize) // want "the sentinel error name `OutOfSizeError2` should conform to the `ErrXxx` format" diff --git a/pkg/analyzer/testdata/src/regular/sentinel_from_types.go b/pkg/analyzer/testdata/src/regular/sentinel_from_types.go index a3ffbf3..ab92863 100644 --- a/pkg/analyzer/testdata/src/regular/sentinel_from_types.go +++ b/pkg/analyzer/testdata/src/regular/sentinel_from_types.go @@ -3,13 +3,16 @@ package regular import "net" var ( - InvalidAddrError = new(net.AddrError) // unsupported - InvalidAddrErr error = new(net.AddrError) // want "the variable name `InvalidAddrErr` should conform to the `ErrXxx` format" + InvalidAddrError = new(net.AddrError) // want "the sentinel error name `InvalidAddrError` should conform to the `ErrXxx` format" + InvalidAddrErr error = new(net.AddrError) // want "the sentinel error name `InvalidAddrErr` should conform to the `ErrXxx` format" NotErr = new(NotErrorType) - Aa = new(someTypeWithPtr) // want "the variable name `Aa` should conform to the `ErrXxx` format" - Bb = someTypeWithoutPtr{} // want "the variable name `Bb` should conform to the `ErrXxx` format" + Aa = new(someTypeWithPtr) // want "the sentinel error name `Aa` should conform to the `ErrXxx` format" + Bb = someTypeWithoutPtr{} // want "the sentinel error name `Bb` should conform to the `ErrXxx` format" + Bbb = someTypeWithPtr{} - cC error = new(someTypeWithPtr) // want "the variable name `cC` should conform to the `errXxx` format" - dD error = someTypeWithoutPtr{} // want "the variable name `dD` should conform to the `errXxx` format" + cC error = new(someTypeWithPtr) // want "the sentinel error name `cC` should conform to the `errXxx` format" + dD error = someTypeWithoutPtr{} // want "the sentinel error name `dD` should conform to the `errXxx` format" + + Alias = InvalidAddrErr // want "the sentinel error name `Alias` should conform to the `ErrXxx` format" ) diff --git a/pkg/analyzer/testdata/src/unusual/generics/generic_types.go b/pkg/analyzer/testdata/src/unusual/generics/generic_types.go index a9e4ce7..b5df47a 100644 --- a/pkg/analyzer/testdata/src/unusual/generics/generic_types.go +++ b/pkg/analyzer/testdata/src/unusual/generics/generic_types.go @@ -12,19 +12,19 @@ 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" +type someErr[T ~string] struct{ Code T } // want "the error 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" +type SomePtrErr[T ~string] struct{ Code T } // want "the error 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" + okErr = &SomePtrError[string]{Code: "200 OK"} // want "the sentinel error 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" + NotFoundErr = SomeError[string]{Code: "Not Found"} // want "the sentinel error 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" + statusCodeError = new(SomePtrError[string]) // want "the sentinel error name `statusCodeError` should conform to the `errXxx` format" + ExplicitError error = new(SomePtrError[string]) // want "the sentinel error name `ExplicitError` should conform to the `ErrXxx` format" ) diff --git a/pkg/analyzer/testdata/src/unusual/generics/issue24.go b/pkg/analyzer/testdata/src/unusual/generics/issue24.go index 48a4919..f2fd121 100644 --- a/pkg/analyzer/testdata/src/unusual/generics/issue24.go +++ b/pkg/analyzer/testdata/src/unusual/generics/issue24.go @@ -12,7 +12,7 @@ type ( Resp any ) -type timeoutErr[REQ Req, RESP Resp] struct { // want "the type name `timeoutErr` should conform to the `xxxError` format" +type timeoutErr[REQ Req, RESP Resp] struct { // want "the error type name `timeoutErr` should conform to the `xxxError` format" err error sending bool } @@ -37,10 +37,10 @@ func (e *timeoutErr[REQ, RESP]) Unwrap() error { 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" +type ValErr[A, B, C, D, E, F any] struct{} // want "the error 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" + tErr error = &timeoutErr[string, string]{err: context.DeadlineExceeded, sending: true} // want "the sentinel error name `tErr` should conform to the `errXxx` format" ) diff --git a/pkg/analyzer/testdata/src/unusual/newfunc/user_new_func.go b/pkg/analyzer/testdata/src/unusual/newfunc/user_new_func.go index 0826d63..ee33c79 100644 --- a/pkg/analyzer/testdata/src/unusual/newfunc/user_new_func.go +++ b/pkg/analyzer/testdata/src/unusual/newfunc/user_new_func.go @@ -1,6 +1,6 @@ package newfunc -var FromUserNewError = new() // want "the variable name `FromUserNewError` should conform to the `ErrXxx` format" +var FromUserNewError = new() // want "the sentinel error name `FromUserNewError` should conform to the `ErrXxx` format" func new() error { return nil