├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── README.md ├── Taskfile.yml ├── go.mod ├── go.sum ├── main.go └── pkg └── analyzer ├── analyzer.go ├── analyzer_test.go ├── facts.go ├── facts_test.go └── testdata └── src ├── regular ├── error_types.go ├── error_types_arrays.go ├── sentinel.go ├── sentinel_from_constants.go ├── sentinel_from_funcs.go ├── sentinel_from_pkg_alias.go └── sentinel_from_types.go └── unusual ├── errortype └── user_error_type.go ├── generics ├── generic_types.go └── issue24.go └── newfunc └── user_new_func.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | ignore: 9 | - dependency-name: "*" 10 | update-types: 11 | - "version-update:semver-minor" 12 | - "version-update:semver-patch" 13 | 14 | - package-ecosystem: "gomod" 15 | directory: "/" 16 | schedule: 17 | interval: "monthly" 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | tags: [ v* ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | env: 11 | GO_VERSION: ^1.24 12 | GOLANGCI_LINT_VERSION: v1.64.7 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | go_install: 19 | if: ${{ !github.event.pull_request.head.repo.fork }} 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: rlespinasse/github-slug-action@v5.0.0 23 | - uses: actions/setup-go@v5 24 | with: 25 | go-version: ${{ env.GO_VERSION }} 26 | - run: cd /tmp && go install github.com/Antonboom/errname@${{ env.version }} && errname -h 27 | env: 28 | version: ${{ env.GITHUB_EVENT_PULL_REQUEST_HEAD_SHA || env.GITHUB_REF_SLUG }} 29 | 30 | lint: 31 | permissions: 32 | contents: read # for actions/checkout to fetch code 33 | pull-requests: read # for golangci/golangci-lint-action to fetch pull requests 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: actions/setup-go@v5 38 | with: 39 | go-version: ${{ env.GO_VERSION }} 40 | - uses: golangci/golangci-lint-action@v6 41 | with: 42 | version: ${{ env.GOLANGCI_LINT_VERSION }} 43 | args: --timeout=5m 44 | 45 | test: 46 | permissions: 47 | checks: write # for shogo82148/actions-goveralls to create a new check based on the results 48 | contents: read # for actions/checkout to fetch code 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v4 52 | - uses: actions/setup-go@v5 53 | with: 54 | go-version: ${{ env.GO_VERSION }} 55 | - run: go test -coverprofile=coverage.out ./... 56 | - uses: shogo82148/actions-goveralls@v1 57 | with: 58 | path-to-profile: coverage.out 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | .DS_Store 4 | coverage.out 5 | *.mem 6 | *.cpu 7 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: true 3 | 4 | issues: 5 | max-same-issues: 0 6 | 7 | linters-settings: 8 | gci: 9 | sections: 10 | - standard 11 | - default 12 | - prefix(github.com/Antonboom/errname) 13 | 14 | linters: 15 | disable-all: true 16 | enable: 17 | - asasalint 18 | - asciicheck 19 | - bidichk 20 | - bodyclose 21 | - copyloopvar 22 | - dogsled 23 | - dupl 24 | - durationcheck 25 | - errcheck 26 | - errchkjson 27 | - errname 28 | - exhaustive 29 | - exptostd 30 | - forbidigo 31 | - gci 32 | - gocheckcompilerdirectives 33 | - gocognit 34 | - goconst 35 | - gocritic 36 | - gocyclo 37 | - godot 38 | - godox 39 | - gofmt 40 | - gofumpt 41 | - goheader 42 | - goimports 43 | - gomoddirectives 44 | - gomodguard 45 | - goprintffuncname 46 | - gosec 47 | - gosimple 48 | - govet 49 | - importas 50 | - ineffassign 51 | - lll 52 | - makezero 53 | - misspell 54 | - mirror 55 | - mnd 56 | - nakedret 57 | - nilerr 58 | - nilnesserr 59 | - nilnil 60 | - nestif 61 | - noctx 62 | - nolintlint 63 | - nosprintfhostport 64 | - prealloc 65 | - predeclared 66 | - reassign 67 | - revive 68 | - sqlclosecheck 69 | - staticcheck 70 | - stylecheck 71 | - tagliatelle 72 | - testableexamples 73 | - thelper 74 | - tparallel 75 | - typecheck 76 | - unconvert 77 | - unparam 78 | - unused 79 | - usestdlibvars 80 | - usetesting 81 | - wastedassign 82 | - whitespace 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Anton Telyshev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # errname 2 | 3 | ![Latest release](https://img.shields.io/github/v/release/Antonboom/errname) 4 | [![CI](https://github.com/Antonboom/errname/actions/workflows/ci.yml/badge.svg)](https://github.com/Antonboom/errname/actions/workflows/ci.yml) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/Antonboom/errname)](https://goreportcard.com/report/github.com/Antonboom/errname) 6 | [![Coverage](https://coveralls.io/repos/github/Antonboom/errname/badge.svg?branch=master)](https://coveralls.io/github/Antonboom/errname?branch=master) 7 | [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) 8 | 9 | Checks that sentinel errors are prefixed with the `Err` and error types 10 | are suffixed with the `Error` or `Errors`. 11 | 12 | ## Installation & usage 13 | 14 | ``` 15 | $ go install github.com/Antonboom/errname@latest 16 | $ errname ./... 17 | ``` 18 | 19 | ## Motivation 20 | 21 | [The convention](https://go.dev/wiki/Errors#naming) states that 22 | > Error types end in "Error" and error variables start with "Err" or "err". 23 | 24 | This can be found in the standard Go library: 25 | 26 | ```go 27 | type AddrError struct{ /* ... */ } 28 | type DecodeError struct{ /*...*/ } 29 | type NoMatchingVersionError struct{ /*...*/ } 30 | type nothingWrittenError struct{ /*...*/ } 31 | type pasteIndicatorError struct{ /*...*/ } 32 | type wrapError struct{ /*...*/ } 33 | 34 | var ErrFinalToken = errors.New("final token") 35 | var ErrRange = errors.New("value out of range") 36 | var ErrUnsupportedAlgorithm = errors.New("x509: cannot verify signature: algorithm unimplemented") 37 | var errMissing = errors.New("cannot find package") 38 | var errNUL = errors.New("unexpected NUL in input") 39 | var errTagValueSpace = errors.New("suspicious space in struct tag value") 40 | ``` 41 | 42 | Also, this can be found in some articles about errors in Go, for 43 | example, [here](https://travix.io/errors-derived-from-constants-in-go-fda6748b4072): 44 | > At first sight you’d think the naming is a bit weird, but that is because of conventions: types are suffixed with 45 | > “Error”, while constants are prefixed with “Err”. So we’re just following the conventions here. 46 | 47 | This is a good rule to improve the consistency of your code. **More uniformity, what could be better?** 48 | 49 | ### What if I think it's bullshit? 50 | 51 | Just don't enable the linter. 52 | 53 | ### Why not [revive/error-naming](https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-naming)? 54 | 55 | At the time of writing this linter, I was unaware of **revive**.
56 | **errname** performs more complex and better checks anyway. 57 | 58 | ## Examples 59 | 60 | ```go 61 | // Bad. 62 | type DecodeErr struct{} // the error type name `DecodeErr` should conform to the `xxxError` format 63 | func (d *DecodeErr) Error() string { /*...*/ } 64 | 65 | // Good. 66 | type DecodeError struct{} 67 | func (d *DecodeError) Error() string { /*...*/ } 68 | ``` 69 | 70 | ```go 71 | // Bad. 72 | var InvalidURLErr = errors.New("invalid url") // the sentinel error name `InvalidURLErr` should conform to the `ErrXxx` format 73 | 74 | // Good. 75 | var ErrInvalidURL = errors.New("invalid url") // or errInvalidURL 76 | ``` 77 | 78 | More examples in [tests](https://github.com/Antonboom/errname/blob/master/pkg/analyzer/facts_test.go). 79 | 80 | ## Assumptions 81 | 82 |
83 | Click to expand 84 | 85 |
86 | 87 | - Only package level variables (and constants) are checked. 88 | - Initialisms are ignored. As a result, all identifiers in a single case are ignored: 89 | 90 | ```go 91 | var EOF = errors.New("end of file") 92 | var eof = errors.New("end of file") 93 | var W = errors.New("single letter error") // on the developer's conscience 94 | var ovflw = errors.New("value is overflow") // on the developer's conscience 95 | ``` 96 | 97 | - The naming of error constructors is not checked. But I recommend keeping it consistent: 98 | 99 | ```go 100 | type DecodeError struct{} 101 | func (d *DecodeError) Error() string { /*...*/ } 102 | 103 | // Bad. 104 | func NewErrDecode() error { 105 | return &DecodeError{} 106 | } 107 | 108 | // Good. 109 | func NewDecodeError() error { 110 | return &DecodeError{} 111 | } 112 | ``` 113 | 114 | - Linter only checks the correctness of the suffix and prefix and their **uniqueness**. The logical meaning of the 115 | identifier remains on the developer's conscience: 116 | 117 | ```go 118 | // Bad. 119 | var ErrExecErr = errors.New("exec query error") 120 | 121 | // Good. 122 | var ErrExecQuery = errors.New("exec query error") 123 | var ErrGdfjnskjdfskf = errors.New("strange error") // on the developer's conscience 124 | ``` 125 | 126 | - For error types over array/slice the `Errors` suffix is also allowed: 127 | 128 | ```go 129 | // Bad. 130 | type ValidationErrs []string 131 | func (ve ValidationErrs) Error() string { /*...*/ } 132 | 133 | // Good. 134 | type ValidationErrors []string 135 | func (ve ValidationErrors) Error() string { /*...*/ } 136 | ``` 137 | 138 |
139 | 140 | ## Large projects examples 141 | 142 |
143 | Golang source code 144 | 145 | ```go 146 | $ errname ./src/... 147 | go/src/runtime/error.go:72:6: the error type name `errorString` should conform to the `xxxError` format 148 | go/src/runtime/error.go:80:6: the error type name `errorAddressString` should conform to the `xxxError` format 149 | go/src/runtime/panic.go:180:5: the sentinel error name `shiftError` should conform to the `errXxx` format 150 | go/src/runtime/panic.go:187:5: the sentinel error name `divideError` should conform to the `errXxx` format 151 | go/src/runtime/panic.go:194:5: the sentinel error name `overflowError` should conform to the `errXxx` format 152 | go/src/runtime/panic.go:201:5: the sentinel error name `floatError` should conform to the `errXxx` format 153 | go/src/runtime/panic.go:208:5: the sentinel error name `memoryError` should conform to the `errXxx` format 154 | go/src/errors/errors.go:63:6: the error type name `errorString` should conform to the `xxxError` format 155 | go/src/math/bits/bits_errors.go:12:5: the sentinel error name `overflowError` should conform to the `errXxx` format 156 | go/src/math/bits/bits_errors.go:15:5: the sentinel error name `divideError` should conform to the `errXxx` format 157 | go/src/syscall/syscall_unix.go:114:6: the error type name `Errno` should conform to the `XxxError` format 158 | go/src/time/format.go:394:5: the sentinel error name `atoiError` should conform to the `errXxx` format 159 | go/src/time/zoneinfo_read.go:110:5: the sentinel error name `badData` should conform to the `errXxx` format 160 | go/src/io/fs/walk.go:15:5: the sentinel error name `SkipDir` should conform to the `ErrXxx` format 161 | go/src/fmt/scan.go:465:5: the sentinel error name `complexError` should conform to the `errXxx` format 162 | go/src/fmt/scan.go:466:5: the sentinel error name `boolError` should conform to the `errXxx` format 163 | go/src/archive/tar/common.go:39:6: the error type name `headerError` should conform to the `xxxError` format 164 | go/src/context/context.go:157:5: the sentinel error name `Canceled` should conform to the `ErrXxx` format 165 | go/src/context/context.go:161:5: the sentinel error name `DeadlineExceeded` should conform to the `ErrXxx` format 166 | go/src/math/big/float.go:77:6: the error type name `ErrNaN` should conform to the `XxxError` format 167 | go/src/crypto/x509/internal/macos/security.go:39:6: the error type name `OSStatus` should conform to the `XxxError` format 168 | go/src/net/cgo_unix.go:34:6: the error type name `addrinfoErrno` should conform to the `xxxError` format 169 | go/src/crypto/x509/x509.go:875:6: the error type name `UnhandledCriticalExtension` should conform to the `XxxError` format 170 | go/src/crypto/x509/pem_decrypt.go:110:5: the sentinel error name `IncorrectPasswordError` should conform to the `ErrXxx` format 171 | go/src/crypto/x509/root.go:18:2: the sentinel error name `systemRootsErr` should conform to the `errXxx` format 172 | go/src/crypto/tls/alert.go:18:2: the sentinel error name `alertCloseNotify` should conform to the `errXxx` format 173 | go/src/crypto/tls/alert.go:19:2: the sentinel error name `alertUnexpectedMessage` should conform to the `errXxx` format 174 | go/src/crypto/tls/alert.go:20:2: the sentinel error name `alertBadRecordMAC` should conform to the `errXxx` format 175 | go/src/crypto/tls/alert.go:21:2: the sentinel error name `alertDecryptionFailed` should conform to the `errXxx` format 176 | go/src/crypto/tls/alert.go:22:2: the sentinel error name `alertRecordOverflow` should conform to the `errXxx` format 177 | go/src/crypto/tls/alert.go:23:2: the sentinel error name `alertDecompressionFailure` should conform to the `errXxx` format 178 | go/src/crypto/tls/alert.go:24:2: the sentinel error name `alertHandshakeFailure` should conform to the `errXxx` format 179 | go/src/crypto/tls/alert.go:25:2: the sentinel error name `alertBadCertificate` should conform to the `errXxx` format 180 | go/src/crypto/tls/alert.go:26:2: the sentinel error name `alertUnsupportedCertificate` should conform to the `errXxx` format 181 | go/src/crypto/tls/alert.go:27:2: the sentinel error name `alertCertificateRevoked` should conform to the `errXxx` format 182 | go/src/crypto/tls/alert.go:28:2: the sentinel error name `alertCertificateExpired` should conform to the `errXxx` format 183 | go/src/crypto/tls/alert.go:29:2: the sentinel error name `alertCertificateUnknown` should conform to the `errXxx` format 184 | go/src/crypto/tls/alert.go:30:2: the sentinel error name `alertIllegalParameter` should conform to the `errXxx` format 185 | go/src/crypto/tls/alert.go:31:2: the sentinel error name `alertUnknownCA` should conform to the `errXxx` format 186 | go/src/crypto/tls/alert.go:32:2: the sentinel error name `alertAccessDenied` should conform to the `errXxx` format 187 | go/src/crypto/tls/alert.go:33:2: the sentinel error name `alertDecodeError` should conform to the `errXxx` format 188 | go/src/crypto/tls/alert.go:34:2: the sentinel error name `alertDecryptError` should conform to the `errXxx` format 189 | go/src/crypto/tls/alert.go:35:2: the sentinel error name `alertExportRestriction` should conform to the `errXxx` format 190 | go/src/crypto/tls/alert.go:36:2: the sentinel error name `alertProtocolVersion` should conform to the `errXxx` format 191 | go/src/crypto/tls/alert.go:37:2: the sentinel error name `alertInsufficientSecurity` should conform to the `errXxx` format 192 | go/src/crypto/tls/alert.go:38:2: the sentinel error name `alertInternalError` should conform to the `errXxx` format 193 | go/src/crypto/tls/alert.go:39:2: the sentinel error name `alertInappropriateFallback` should conform to the `errXxx` format 194 | go/src/crypto/tls/alert.go:40:2: the sentinel error name `alertUserCanceled` should conform to the `errXxx` format 195 | go/src/crypto/tls/alert.go:41:2: the sentinel error name `alertNoRenegotiation` should conform to the `errXxx` format 196 | go/src/crypto/tls/alert.go:42:2: the sentinel error name `alertMissingExtension` should conform to the `errXxx` format 197 | go/src/crypto/tls/alert.go:43:2: the sentinel error name `alertUnsupportedExtension` should conform to the `errXxx` format 198 | go/src/crypto/tls/alert.go:44:2: the sentinel error name `alertCertificateUnobtainable` should conform to the `errXxx` format 199 | go/src/crypto/tls/alert.go:45:2: the sentinel error name `alertUnrecognizedName` should conform to the `errXxx` format 200 | go/src/crypto/tls/alert.go:46:2: the sentinel error name `alertBadCertificateStatusResponse` should conform to the `errXxx` format 201 | go/src/crypto/tls/alert.go:47:2: the sentinel error name `alertBadCertificateHashValue` should conform to the `errXxx` format 202 | go/src/crypto/tls/alert.go:48:2: the sentinel error name `alertUnknownPSKIdentity` should conform to the `errXxx` format 203 | go/src/crypto/tls/alert.go:49:2: the sentinel error name `alertCertificateRequired` should conform to the `errXxx` format 204 | go/src/crypto/tls/alert.go:50:2: the sentinel error name `alertNoApplicationProtocol` should conform to the `errXxx` format 205 | go/src/path/filepath/path.go:337:5: the sentinel error name `SkipDir` should conform to the `ErrXxx` format 206 | go/src/net/http/h2_bundle.go:1016:5: the sentinel error name `http2errReadEmpty` should conform to the `errXxx` format 207 | go/src/net/http/h2_bundle.go:1212:2: the sentinel error name `http2errMixPseudoHeaderTypes` should conform to the `errXxx` format 208 | go/src/net/http/h2_bundle.go:1213:2: the sentinel error name `http2errPseudoAfterRegular` should conform to the `errXxx` format 209 | go/src/net/http/h2_bundle.go:1712:5: the sentinel error name `http2ErrFrameTooLarge` should conform to the `errXxx` format 210 | go/src/net/http/h2_bundle.go:1866:2: the sentinel error name `http2errStreamID` should conform to the `errXxx` format 211 | go/src/net/http/h2_bundle.go:1867:2: the sentinel error name `http2errDepStreamID` should conform to the `errXxx` format 212 | go/src/net/http/h2_bundle.go:1868:2: the sentinel error name `http2errPadLength` should conform to the `errXxx` format 213 | go/src/net/http/h2_bundle.go:1869:2: the sentinel error name `http2errPadBytes` should conform to the `errXxx` format 214 | go/src/net/http/h2_bundle.go:3400:5: the sentinel error name `http2errTimeout` should conform to the `errXxx` format 215 | go/src/net/http/h2_bundle.go:3519:5: the sentinel error name `http2errClosedPipeWrite` should conform to the `errXxx` format 216 | go/src/net/http/h2_bundle.go:3629:2: the sentinel error name `http2errClientDisconnected` should conform to the `errXxx` format 217 | go/src/net/http/h2_bundle.go:3630:2: the sentinel error name `http2errClosedBody` should conform to the `errXxx` format 218 | go/src/net/http/h2_bundle.go:3631:2: the sentinel error name `http2errHandlerComplete` should conform to the `errXxx` format 219 | go/src/net/http/h2_bundle.go:3632:2: the sentinel error name `http2errStreamClosed` should conform to the `errXxx` format 220 | go/src/net/http/h2_bundle.go:4526:5: the sentinel error name `http2errPrefaceTimeout` should conform to the `errXxx` format 221 | go/src/net/http/h2_bundle.go:4746:5: the sentinel error name `http2errHandlerPanicked` should conform to the `errXxx` format 222 | go/src/net/http/h2_bundle.go:6287:2: the sentinel error name `http2ErrRecursivePush` should conform to the `errXxx` format 223 | go/src/net/http/h2_bundle.go:6288:2: the sentinel error name `http2ErrPushLimitReached` should conform to the `errXxx` format 224 | go/src/net/http/h2_bundle.go:6930:5: the sentinel error name `http2ErrNoCachedConn` should conform to the `errXxx` format 225 | go/src/net/http/h2_bundle.go:7016:2: the sentinel error name `http2errClientConnClosed` should conform to the `errXxx` format 226 | go/src/net/http/h2_bundle.go:7017:2: the sentinel error name `http2errClientConnUnusable` should conform to the `errXxx` format 227 | go/src/net/http/h2_bundle.go:7018:2: the sentinel error name `http2errClientConnGotGoAway` should conform to the `errXxx` format 228 | go/src/net/http/h2_bundle.go:7471:5: the sentinel error name `http2errRequestCanceled` should conform to the `errXxx` format 229 | go/src/net/http/h2_bundle.go:7803:2: the sentinel error name `http2errStopReqBodyWrite` should conform to the `errXxx` format 230 | go/src/net/http/h2_bundle.go:7806:2: the sentinel error name `http2errStopReqBodyWriteAndCancel` should conform to the `errXxx` format 231 | go/src/net/http/h2_bundle.go:7808:2: the sentinel error name `http2errReqBodyTooLong` should conform to the `errXxx` format 232 | go/src/net/http/h2_bundle.go:8667:5: the sentinel error name `http2errClosedResponseBody` should conform to the `errXxx` format 233 | go/src/net/http/h2_bundle.go:9021:2: the sentinel error name `http2errResponseHeaderListSize` should conform to the `errXxx` format 234 | go/src/net/http/h2_bundle.go:9022:2: the sentinel error name `http2errRequestHeaderListSize` should conform to the `errXxx` format 235 | go/src/go/scanner/errors.go:37:6: the error type name `ErrorList` should conform to the `XxxError` format 236 | go/src/html/template/template.go:34:5: the sentinel error name `escapeOK` should conform to the `errXxx` format 237 | go/src/image/png/reader.go:128:5: the sentinel error name `chunkOrderError` should conform to the `errXxx` format 238 | go/src/bufio/scan_test.go:308:5: the sentinel error name `testError` should conform to the `errXxx` format 239 | go/src/crypto/tls/handshake_client_test.go:1993:5: the sentinel error name `brokenConnErr` should conform to the `errXxx` format 240 | go/src/database/sql/sql_test.go:4281:5: the sentinel error name `pingError` should conform to the `errXxx` format 241 | go/src/errors/wrap_test.go:216:6: the error type name `errorT` should conform to the `xxxError` format 242 | go/src/errors/wrap_test.go:229:6: the error type name `errorUncomparable` should conform to the `xxxError` format 243 | go/src/fmt/errors_test.go:75:6: the error type name `errString` should conform to the `xxxError` format 244 | go/src/html/template/exec_test.go:233:5: the sentinel error name `myError` should conform to the `errXxx` format 245 | go/src/html/template/exec_test.go:1313:5: the sentinel error name `alwaysError` should conform to the `errXxx` format 246 | go/src/net/http/transport_test.go:6280:5: the sentinel error name `timeoutProtoErr` should conform to the `errXxx` format 247 | go/src/text/template/exec_test.go:229:5: the sentinel error name `myError` should conform to the `errXxx` format 248 | go/src/text/template/exec_test.go:1305:5: the sentinel error name `alwaysError` should conform to the `errXxx` format 249 | ``` 250 | 251 |
252 | 253 |
254 | Traefik 255 | 256 | ```go 257 | $ errname./... 258 | # no issues 259 | ``` 260 | 261 |
262 | 263 |
264 | Terraform 265 | 266 | ```go 267 | $ errname./... 268 | terraform/internal/getmodules/file_detector.go:59:6: the error type name `MaybeRelativePathErr` should conform to the `XxxError` format 269 | terraform/internal/getproviders/errors.go:13:6: the error type name `ErrHostNoProviders` should conform to the `XxxError` format 270 | terraform/internal/getproviders/errors.go:39:6: the error type name `ErrHostUnreachable` should conform to the `XxxError` format 271 | terraform/internal/getproviders/errors.go:57:6: the error type name `ErrUnauthorized` should conform to the `XxxError` format 272 | terraform/internal/getproviders/errors.go:80:6: the error type name `ErrProviderNotFound` should conform to the `XxxError` format 273 | terraform/internal/getproviders/errors.go:104:6: the error type name `ErrRegistryProviderNotKnown` should conform to the `XxxError` format 274 | terraform/internal/getproviders/errors.go:123:6: the error type name `ErrPlatformNotSupported` should conform to the `XxxError` format 275 | terraform/internal/getproviders/errors.go:159:6: the error type name `ErrProtocolNotSupported` should conform to the `XxxError` format 276 | terraform/internal/getproviders/errors.go:181:6: the error type name `ErrQueryFailed` should conform to the `XxxError` format 277 | terraform/internal/getproviders/errors.go:219:6: the error type name `ErrRequestCanceled` should conform to the `XxxError` format 278 | terraform/internal/registry/errors.go:10:6: the error type name `errModuleNotFound` should conform to the `xxxError` format 279 | terraform/internal/backend/remote-state/consul/client.go:36:5: the sentinel error name `lostLockErr` should conform to the `errXxx` format 280 | terraform/internal/command/cliconfig/credentials.go:408:6: the error type name `ErrUnwritableHostCredentials` should conform to the `XxxError` format 281 | ``` 282 | 283 |
284 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | # https://taskfile.dev/#/installation 2 | version: '3' 3 | 4 | silent: true 5 | 6 | tasks: 7 | default: 8 | cmds: 9 | - task: tools:install 10 | - task: tidy 11 | - task: fmt 12 | - task: lint 13 | - task: test 14 | - task: install 15 | 16 | tools:install: 17 | - echo "Install local tools..." 18 | - (which gci > /dev/null) || go install github.com/daixiang0/gci@latest 19 | - (which gofumpt > /dev/null) || go install mvdan.cc/gofumpt@latest 20 | 21 | tidy: 22 | cmds: 23 | - echo "Tidy..." 24 | - go mod tidy 25 | 26 | fmt: 27 | cmds: 28 | - echo "Fmt..." 29 | - gofumpt -w . 30 | - gci write -s standard -s default -s "Prefix(github.com/Antonboom/errname)" . 2> /dev/null 31 | 32 | lint: 33 | cmds: 34 | - echo "Lint..." 35 | - golangci-lint run --fix ./... 36 | 37 | test: 38 | cmds: 39 | - echo "Tests..." 40 | - go test ./... 41 | 42 | test:coverage: 43 | cmds: 44 | - echo "Tests with coverage..." 45 | - go test -coverprofile=coverage.out ./... 46 | 47 | install: 48 | cmds: 49 | - echo "Install..." 50 | - go install ./... 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Antonboom/errname 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require golang.org/x/tools v0.32.0 8 | 9 | require ( 10 | golang.org/x/mod v0.24.0 // indirect 11 | golang.org/x/sync v0.13.0 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 2 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 4 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 5 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 6 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 7 | golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= 8 | golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= 9 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "golang.org/x/tools/go/analysis/singlechecker" 5 | 6 | "github.com/Antonboom/errname/pkg/analyzer" 7 | ) 8 | 9 | func main() { 10 | singlechecker.Main(analyzer.New()) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/analyzer/analyzer.go: -------------------------------------------------------------------------------- 1 | package analyzer 2 | 3 | import ( 4 | "go/ast" 5 | "go/token" 6 | "go/types" 7 | "unicode" 8 | 9 | "golang.org/x/tools/go/analysis" 10 | "golang.org/x/tools/go/analysis/passes/inspect" 11 | "golang.org/x/tools/go/ast/inspector" 12 | ) 13 | 14 | // New returns new errname analyzer. 15 | func New() *analysis.Analyzer { 16 | return &analysis.Analyzer{ 17 | Name: "errname", 18 | Doc: "Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.", 19 | Run: run, 20 | Requires: []*analysis.Analyzer{inspect.Analyzer}, 21 | } 22 | } 23 | 24 | func run(pass *analysis.Pass) (interface{}, error) { 25 | insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 26 | 27 | insp.Nodes([]ast.Node{ 28 | (*ast.TypeSpec)(nil), 29 | (*ast.ValueSpec)(nil), 30 | (*ast.FuncDecl)(nil), 31 | }, func(node ast.Node, push bool) bool { 32 | if !push { 33 | return false 34 | } 35 | 36 | switch v := node.(type) { 37 | case *ast.FuncDecl: 38 | return false 39 | 40 | case *ast.ValueSpec: 41 | if len(v.Names) != 1 { 42 | return false 43 | } 44 | ident := v.Names[0] 45 | 46 | if exprImplementsError(pass, ident) && !isValidErrorVarName(ident.Name) { 47 | reportAboutSentinelError(pass, v.Pos(), ident.Name) 48 | } 49 | return false 50 | 51 | case *ast.TypeSpec: 52 | tt := pass.TypesInfo.TypeOf(v.Name) 53 | if tt == nil { 54 | return false 55 | } 56 | // NOTE(a.telyshev): Pointer is the hack against Error() method with pointer receiver. 57 | if !typeImplementsError(types.NewPointer(tt)) { 58 | return false 59 | } 60 | 61 | name := v.Name.Name 62 | if _, ok := v.Type.(*ast.ArrayType); ok { 63 | if !isValidErrorArrayTypeName(name) { 64 | reportAboutArrayErrorType(pass, v.Pos(), name) 65 | } 66 | } else if !isValidErrorTypeName(name) { 67 | reportAboutErrorType(pass, v.Pos(), name) 68 | } 69 | return false 70 | } 71 | 72 | return true 73 | }) 74 | 75 | return nil, nil //nolint:nilnil 76 | } 77 | 78 | func reportAboutErrorType(pass *analysis.Pass, typePos token.Pos, typeName string) { 79 | var form string 80 | if unicode.IsLower([]rune(typeName)[0]) { 81 | form = "xxxError" 82 | } else { 83 | form = "XxxError" 84 | } 85 | 86 | pass.Reportf(typePos, "the error type name `%s` should conform to the `%s` format", typeName, form) 87 | } 88 | 89 | func reportAboutArrayErrorType(pass *analysis.Pass, typePos token.Pos, typeName string) { 90 | var forms string 91 | if unicode.IsLower([]rune(typeName)[0]) { 92 | forms = "`xxxErrors` or `xxxError`" 93 | } else { 94 | forms = "`XxxErrors` or `XxxError`" 95 | } 96 | 97 | pass.Reportf(typePos, "the error type name `%s` should conform to the %s format", typeName, forms) 98 | } 99 | 100 | func reportAboutSentinelError(pass *analysis.Pass, pos token.Pos, varName string) { 101 | var form string 102 | if unicode.IsLower([]rune(varName)[0]) { 103 | form = "errXxx" 104 | } else { 105 | form = "ErrXxx" 106 | } 107 | pass.Reportf(pos, "the sentinel error name `%s` should conform to the `%s` format", varName, form) 108 | } 109 | -------------------------------------------------------------------------------- /pkg/analyzer/analyzer_test.go: -------------------------------------------------------------------------------- 1 | package analyzer 2 | 3 | import ( 4 | "testing" 5 | 6 | "golang.org/x/tools/go/analysis/analysistest" 7 | ) 8 | 9 | func TestErrName(t *testing.T) { 10 | pkgs := []string{ 11 | "regular", 12 | "unusual/errortype", 13 | "unusual/generics", 14 | "unusual/newfunc", 15 | } 16 | analysistest.Run(t, analysistest.TestData(), New(), pkgs...) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/analyzer/facts.go: -------------------------------------------------------------------------------- 1 | package analyzer 2 | 3 | import ( 4 | "go/ast" 5 | "go/types" 6 | "strings" 7 | "unicode" 8 | 9 | "golang.org/x/tools/go/analysis" 10 | ) 11 | 12 | var errorIface = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) 13 | 14 | func exprImplementsError(pass *analysis.Pass, e ast.Expr) bool { 15 | return typeImplementsError(pass.TypesInfo.TypeOf(e)) 16 | } 17 | 18 | func typeImplementsError(t types.Type) bool { 19 | return t != nil && types.Implements(t, errorIface) 20 | } 21 | 22 | func isValidErrorTypeName(s string) bool { 23 | if isInitialism(s) { 24 | return true 25 | } 26 | 27 | words := split(s) 28 | wordsCnt := wordsCount(words) 29 | 30 | if wordsCnt["error"] != 1 { 31 | return false 32 | } 33 | return words[len(words)-1] == "error" 34 | } 35 | 36 | func isValidErrorArrayTypeName(s string) bool { 37 | if isInitialism(s) { 38 | return true 39 | } 40 | 41 | words := split(s) 42 | wordsCnt := wordsCount(words) 43 | 44 | if wordsCnt["errors"] != 1 && wordsCnt["error"] != 1 { 45 | return false 46 | } 47 | 48 | lastWord := words[len(words)-1] 49 | return lastWord == "errors" || lastWord == "error" 50 | } 51 | 52 | func isValidErrorVarName(s string) bool { 53 | if isInitialism(s) { 54 | return true 55 | } 56 | 57 | words := split(s) 58 | wordsCnt := wordsCount(words) 59 | 60 | if wordsCnt["err"] != 1 { 61 | return false 62 | } 63 | return words[0] == "err" 64 | } 65 | 66 | func isInitialism(s string) bool { 67 | return strings.ToLower(s) == s || strings.ToUpper(s) == s 68 | } 69 | 70 | func split(s string) []string { 71 | var words []string 72 | ss := []rune(s) 73 | 74 | var b strings.Builder 75 | b.WriteRune(ss[0]) 76 | 77 | for _, r := range ss[1:] { 78 | if unicode.IsUpper(r) { 79 | words = append(words, strings.ToLower(b.String())) 80 | b.Reset() 81 | } 82 | b.WriteRune(r) 83 | } 84 | 85 | words = append(words, strings.ToLower(b.String())) 86 | return words 87 | } 88 | 89 | func wordsCount(w []string) map[string]int { 90 | result := make(map[string]int, len(w)) 91 | for _, ww := range w { 92 | result[ww]++ 93 | } 94 | return result 95 | } 96 | -------------------------------------------------------------------------------- /pkg/analyzer/facts_test.go: -------------------------------------------------------------------------------- 1 | package analyzer 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_isValidErrorTypeName_validTypes(t *testing.T) { 8 | for _, tt := range []string{ 9 | "AddrError", 10 | "AmbiguousImportError", 11 | "BuildError", 12 | "BuildListError", 13 | "CertificateInvalidError", 14 | "CheckEqualError", 15 | "CheckError", 16 | "ConstraintError", 17 | "ConstraintViolationError", 18 | "CustomError", 19 | "CycleInRequiresGraphError", 20 | "DLLError", 21 | "DNSConfigError", 22 | "DNSError", 23 | "DeadlineExceededError", 24 | "DecodeError", 25 | "DecodingError", 26 | "DirectImportFromImplicitDependencyError", 27 | "DownloadDirPartialError", 28 | "EmbedError", 29 | "Error", 30 | "ERROR", 31 | "ExecError", 32 | "ExitError", 33 | "FileError", 34 | "FlagNotDefinedError", 35 | "FormatError", 36 | "HTTPError", 37 | "HostnameError", 38 | "ImportMismatchError", 39 | "ImportMissingError", 40 | "ImportMissingSumError", 41 | "InvalidPathError", 42 | "InvalidSignatureError", 43 | "InvalidUTF8Error", 44 | "InvalidUnmarshalError", 45 | "InvalidVersionError", 46 | "LinkError", 47 | "MarshalerError", 48 | "MatchError", 49 | "ModuleError", 50 | "ModuleRetractedError", 51 | "MultiplePackageError", 52 | "MyError", 53 | "NoGoError", 54 | "NoMatchingVersionError", 55 | "NoPatchBaseError", 56 | "NonFlagError", 57 | "NumError", 58 | "OpError", 59 | "PackageError", 60 | "PackageNotInModuleError", 61 | "ParseError", 62 | "PathError", 63 | "ProtocolError", 64 | "PtyError", 65 | "QueryMatchesMainModuleError", 66 | "QueryMatchesPackagesInMainModuleError", 67 | "ReadError", 68 | "RecordHeaderError", 69 | "ReferralError", 70 | "RunError", 71 | "StructuralError", 72 | "SyntaxError", 73 | "SyscallError", 74 | "SystemRootsError", 75 | "TagPathError", 76 | "TypeAssertionError", 77 | "UnknownAuthorityError", 78 | "UnknownLineError", 79 | "UnknownRevisionError", 80 | "UnknownVerifierError", 81 | "UnmarshalFieldError", 82 | "UnmarshalTypeError", 83 | "UnsupportedTypeError", 84 | "UnsupportedValueError", 85 | "UnverifiedNoteError", 86 | "VCSError", 87 | "ValueError", 88 | "WildcardInFirstElementError", 89 | "WriteError", 90 | "alwaysError", 91 | "ambiguousVerifierError", 92 | "badPathError", 93 | "boundsError", 94 | "cgoError", 95 | "conflictError", 96 | "deadlineExceededError", 97 | "entryNotFoundError", 98 | "err", 99 | "error", 100 | "excludedError", 101 | "formatError", 102 | "goModDirtyError", 103 | "gobError", 104 | "http2GoAwayError", 105 | "http2StreamError", 106 | "http2connError", 107 | "http2goAwayFlowError", 108 | "http2httpError", 109 | "http2noCachedConnError", 110 | "http2resAndError", 111 | "httpError", 112 | "importError", 113 | "invalidImportError", 114 | "invalidUnmarshalError", 115 | "issue22091Error", 116 | "jsonError", 117 | "labelError", 118 | "localError", 119 | "lookupDisabledError", 120 | "mainPackageError", 121 | "myError", 122 | "nestedError", 123 | "noCommitsError", 124 | "noWrapError", 125 | "notExistError", 126 | "nothingWrittenError", 127 | "onceError", 128 | "osError", 129 | "panicError", 130 | "pasteIndicatorError", 131 | "permanentError", 132 | "queryDisabledError", 133 | "requestBodyReadError", 134 | "responseAndError", 135 | "retractionLoadingError", 136 | "scanError", 137 | "statusError", 138 | "stringError", 139 | "sumMissingError", 140 | "summaryError", 141 | "temporaryError", 142 | "testError", 143 | "timeoutError", 144 | "timeoutTemporaryError", 145 | "tlsHandshakeTimeoutError", 146 | "transportReadFromServerError", 147 | "unexportedCustomError", 148 | "unexportedError", 149 | "unsupportedTEError", 150 | "wantedError", 151 | "withStackError", 152 | "withTimeoutError", 153 | "wrapError", 154 | "writeError", 155 | "writerWithReadFromError", 156 | "zipError", 157 | } { 158 | if !isValidErrorTypeName(tt) { 159 | t.Errorf("%q must be valid error type name", tt) 160 | } 161 | } 162 | } 163 | 164 | func Test_isValidErrorTypeName_invalidTypes(t *testing.T) { 165 | for _, tt := range []string{ 166 | "AmbiguousImportErr", 167 | "BuildErr", 168 | "CertificateInvalidErr", 169 | "CheckEqualErrorErr", 170 | "CheckEqualErrorError", 171 | "Err", 172 | "ErrorBuildList", 173 | "ErrorBuildListError", 174 | "errWithStack", 175 | "errZIP", 176 | "nestedErr", 177 | "withStackErr", 178 | "withStackERR", 179 | "withStackERROR", 180 | } { 181 | if isValidErrorTypeName(tt) { 182 | t.Errorf("%q must be invalid error type name", tt) 183 | } 184 | } 185 | } 186 | 187 | func Test_isValidErrorArrayTypeName(t *testing.T) { 188 | for _, tt := range []string{ 189 | "ValidationErrors", 190 | "validationErrors", 191 | "ERRORS", 192 | "errors", 193 | "IncompatiblePairError", 194 | "multiError", 195 | } { 196 | if !isValidErrorArrayTypeName(tt) { 197 | t.Errorf("%q must be valid error array type name", tt) 198 | } 199 | } 200 | 201 | for _, tt := range []string{ 202 | "ErrorsFromValidation", 203 | "errorsFromValidation", 204 | "ErrorMulti", 205 | "ErrorsOfProcessing", 206 | "processingErrs", 207 | "ValidationErr", 208 | } { 209 | if isValidErrorArrayTypeName(tt) { 210 | t.Errorf("%q must be invalid error array type name", tt) 211 | } 212 | } 213 | } 214 | 215 | func Test_isValidErrorVarName_validVars(t *testing.T) { 216 | for _, tt := range []string{ 217 | "EOF", 218 | "ErrAbortHandler", 219 | "ErrAdvanceTooFar", 220 | "ErrAlgorithm", 221 | "ErrBadConn", 222 | "ErrBadName", 223 | "ErrBadPattern", 224 | "ErrBadReadCount", 225 | "ErrBadStat", 226 | "ErrBareQuote", 227 | "ErrBodyNotAllowed", 228 | "ErrBodyReadAfterClose", 229 | "ErrBufferFull", 230 | "ErrChecksum", 231 | "ErrClosed", 232 | "ErrClosedPipe", 233 | "ErrConnClosed", 234 | "ErrConnDone", 235 | "ErrContentLength", 236 | "ErrDecryption", 237 | "ErrDictionary", 238 | "ErrDisallowed", 239 | "ErrEndOfSpan", 240 | "ErrExecError", 241 | "ErrExist", 242 | "ErrFieldCount", 243 | "ErrFieldTooLong", 244 | "ErrFileClosing", 245 | "ErrFinalToken", 246 | "ErrFlagTerminator", 247 | "ErrFormat", 248 | "ErrFrameTooLarge", 249 | "ErrGONOSUMDB", 250 | "ErrHandlerTimeout", 251 | "ErrHeader", 252 | "ErrHeaderNotPresent", 253 | "ErrHelp", 254 | "ErrHijacked", 255 | "ErrInvalid", 256 | "ErrInvalidHuffman", 257 | "ErrInvalidMediaParameter", 258 | "ErrInvalidMode", 259 | "ErrInvalidPublicKey", 260 | "ErrInvalidUnreadByte", 261 | "ErrInvalidUnreadRune", 262 | "ErrLength", 263 | "ErrLineTooLong", 264 | "ErrMessageTooLarge", 265 | "ErrMessageTooLong", 266 | "ErrMissingFile", 267 | "ErrNegativeAdvance", 268 | "ErrNegativeCount", 269 | "ErrNoCookie", 270 | "ErrNoDeadline", 271 | "ErrNoLocation", 272 | "ErrNoModRoot", 273 | "ErrNoProgress", 274 | "ErrNoRows", 275 | "ErrNoSymbols", 276 | "ErrNoTrustSettings", 277 | "ErrNotExist", 278 | "ErrNotFound", 279 | "ErrNotMangledName", 280 | "ErrNotPollable", 281 | "ErrNotStarted", 282 | "ErrNotSupported", 283 | "ErrPermission", 284 | "ErrProcessDone", 285 | "ErrPushLimitReached", 286 | "ErrQuote", 287 | "ErrRange", 288 | "ErrRecursivePush", 289 | "ErrRemoveArgument", 290 | "ErrRequestAborted", 291 | "ErrSectionDone", 292 | "ErrSecurity", 293 | "ErrServerClosed", 294 | "ErrShortBuffer", 295 | "ErrShortDst", 296 | "ErrShortSrc", 297 | "ErrShortStat", 298 | "ErrShortWrite", 299 | "ErrShutdown", 300 | "ErrSkip", 301 | "ErrSkipAltProtocol", 302 | "ErrStringLength", 303 | "ErrSyntax", 304 | "ErrTimeout", 305 | "ErrTooLarge", 306 | "ErrTooLong", 307 | "ErrTooManyErrors", 308 | "ErrTrailingComma", 309 | "ErrTruncated", 310 | "ErrTxDone", 311 | "ErrUnexpectedEOF", 312 | "ErrUnexpectedType", 313 | "ErrUnknownPC", 314 | "ErrUnrecognized", 315 | "ErrUnsupportedAlgorithm", 316 | "ErrUseLastResponse", 317 | "ErrVerification", 318 | "ErrWriteAfterClose", 319 | "ErrWriteAfterFlush", 320 | "ErrWriteConflict", 321 | "ErrWriteToConnected", 322 | "ErrWriteTooLong", 323 | "errBad", 324 | "errBadCgo", 325 | "errBadComparison", 326 | "errBadComparisonType", 327 | "errBadCount", 328 | "errBadELF", 329 | "errBadPixel", 330 | "errBadTestInputs", 331 | "errBadType", 332 | "errBadUint", 333 | "errBaseLen", 334 | "errCalcLen", 335 | "errCallerOwnsConn", 336 | "errCanceled", 337 | "errCannotMarshalDNSMessage", 338 | "errCannotRewind", 339 | "errCannotUnmarshalDNSMessage", 340 | "errClientConnClosed", 341 | "errClientConnGotGoAway", 342 | "errClientConnUnusable", 343 | "errClientDisconnected", 344 | "errClientKeyExchange", 345 | "errCloseConn", 346 | "errCloseIdle", 347 | "errCloseIdleConns", 348 | "errClosed", 349 | "errClosedBody", 350 | "errClosedPipeWrite", 351 | "errClosedResponseBody", 352 | "errComplex", 353 | "errCompressedSRV", 354 | "errConnBroken", 355 | "errCorruptArchive", 356 | "errCorruptObject", 357 | "errDBClosed", 358 | "errDepStreamID", 359 | "errDial", 360 | "errDirectoryNotFound", 361 | "errDiscardedBytes", 362 | "errEarlyCloseWrite", 363 | "errFail", 364 | "errFake", 365 | "errFakeConnSessionDirty", 366 | "errFakeRoundTrip", 367 | "errFileTooNew", 368 | "errGoBuildWithoutBuild", 369 | "errGoModCase", 370 | "errHandlerComplete", 371 | "errHandlerPanicked", 372 | "errHgArchivalTxt", 373 | "errIO", 374 | "errIdleConnTimeout", 375 | "errIllegalDomain", 376 | "errInconsistentByteCount", 377 | "errInternal", 378 | "errInvalSep", 379 | "errInvalidAddr", 380 | "errInvalidDNSResponse", 381 | "errInvalidDelim", 382 | "errInvalidInterface", 383 | "errInvalidInterfaceIndex", 384 | "errInvalidInterfaceName", 385 | "errInvalidMessage", 386 | "errInvalidPtr", 387 | "errInvalidSigner", 388 | "errInvalidWord", 389 | "errInvalidWrite", 390 | "errKeepAlivesDisabled", 391 | "errLameReferral", 392 | "errLeadingInt", 393 | "errLocation", 394 | "errLongExtra", 395 | "errLongName", 396 | "errMain", 397 | "errMalformedDomain", 398 | "errMalformedNote", 399 | "errMalformedRecord", 400 | "errMalformedTree", 401 | "errMessageMismatch", 402 | "errMessageTooShort", 403 | "errMissData", 404 | "errMissing", 405 | "errMissingAddress", 406 | "errMissingHost", 407 | "errMissingParams", 408 | "errMissingReadDir", 409 | "errMissingSeek", 410 | "errMissingU8Prefix", 411 | "errMixPseudoHeaderTypes", 412 | "errMultipleGoBuild", 413 | "errNUL", 414 | "errNeedMore", 415 | "errNegativeRead", 416 | "errNegativeWrite", 417 | "errNilPanicOrGoexit", 418 | "errNilPtr", 419 | "errNilResouceBody", 420 | "errNoAnswerFromDNSServer", 421 | "errNoAvailableAddress", 422 | "errNoAvailableInterface", 423 | "errNoBody", 424 | "errNoBuildID", 425 | "errNoCertificates", 426 | "errNoChange", 427 | "errNoComparison", 428 | "errNoDigits", 429 | "errNoFallocate", 430 | "errNoHostname", 431 | "errNoModules", 432 | "errNoOverlap", 433 | "errNoRows", 434 | "errNoSuchHost", 435 | "errNoSuchInterface", 436 | "errNoSuchMulticastInterface", 437 | "errNoSuitableAddress", 438 | "errNonCanonicalName", 439 | "errNotAbsolute", 440 | "errNotConstraint", 441 | "errNotDir", 442 | "errNotEnough", 443 | "errNotObject", 444 | "errNotParsed", 445 | "errNotPermitted", 446 | "errNotRegular", 447 | "errOffline", 448 | "errOffset", 449 | "errOpen", 450 | "errOutOfCodes", 451 | "errPadBytes", 452 | "errPadLength", 453 | "errParse", 454 | "errPathNotClean", 455 | "errPathNotRelative", 456 | "errPatternHasSeparator", 457 | "errPkgIsBuiltin", 458 | "errPkgIsGorootSrc", 459 | "errPrefaceTimeout", 460 | "errPrintedOutput", 461 | "errProofFailed", 462 | "errPseudoAfterRegular", 463 | "errPseudoSyntax", 464 | "errPublicExponentLarge", 465 | "errPublicExponentSmall", 466 | "errPublicModulus", 467 | "errRange", 468 | "errRawToken", 469 | "errReadEmpty", 470 | "errReadLoopExiting", 471 | "errReadOnClosedResBody", 472 | "errRemoteOriginNotFound", 473 | "errReqBodyTooLong", 474 | "errRequestCanceled", 475 | "errRequestCanceledConn", 476 | "errRequestHeaderListSize", 477 | "errResTooLong", 478 | "errReserved", 479 | "errResourceLen", 480 | "errResponseHeaderListSize", 481 | "errRevQuery", 482 | "errRowsClosed", 483 | "errSeeker", 484 | "errSegTooLong", 485 | "errSegmentSelector", 486 | "errServerClosedIdle", 487 | "errServerKeyExchange", 488 | "errServerMisbehaving", 489 | "errServerTemporarilyMisbehaving", 490 | "errShortBuffer", 491 | "errShortInternal", 492 | "errShutdown", 493 | "errSignerAlg", 494 | "errSignerHash", 495 | "errSignerID", 496 | "errStopReqBodyWrite", 497 | "errStopReqBodyWriteAndCancel", 498 | "errStreamClosed", 499 | "errStreamID", 500 | "errStringTooLong", 501 | "errSubmoduleDir", 502 | "errSubmoduleFile", 503 | "errSymlink", 504 | "errSyntax", 505 | "errTagKeySyntax", 506 | "errTagSpace", 507 | "errTagSyntax", 508 | "errTagValueSpace", 509 | "errTagValueSyntax", 510 | "errTestScanWrap", 511 | "errTooBig", 512 | "errTooLarge", 513 | "errTooManyAdditionals", 514 | "errTooManyAnswers", 515 | "errTooManyAuthorities", 516 | "errTooManyIdle", 517 | "errTooManyIdleHost", 518 | "errTooManyPtr", 519 | "errTooManyQuestions", 520 | "errTooMuch", 521 | "errTrailerEOF", 522 | "errTruncatedArchive", 523 | "errUnknownSite", 524 | "errUnreadByte", 525 | "errUnrefData", 526 | "errUnsupportedMessage", 527 | "errVCS", 528 | "errValue", 529 | "errVendored", 530 | "errVerifierAlg", 531 | "errVerifierHash", 532 | "errVerifierID", 533 | "errVerifyMode", 534 | "errVersionChange", 535 | "errWhence", 536 | "errWriteAtInAppendMode", 537 | "errWriteHole", 538 | "errZeroParam", 539 | "errZeroSegLen", 540 | } { 541 | if !isValidErrorVarName(tt) { 542 | t.Errorf("%q must be valid error var name", tt) 543 | } 544 | } 545 | } 546 | 547 | func Test_isValidErrorVarName_invalidVars(t *testing.T) { 548 | for _, tt := range []string{ 549 | "Canceled", 550 | "ERRORExec", 551 | "ERRExec", 552 | "ErrExecErr", 553 | "ErrorExec", 554 | "IncorrectPasswordError", 555 | "SkipDir", 556 | "alwaysError", 557 | "atoiError", 558 | "badData", 559 | "badErr", 560 | "boolError", 561 | "brokenConnErr", 562 | "checkErr", 563 | "complexError", 564 | "fakeErr", 565 | "isInternal", 566 | "isNotExistErr", 567 | "myError", 568 | "outErr", 569 | "pingError", 570 | "sentinelErr", 571 | "someErr", 572 | "statErr", 573 | "testError", 574 | "timeoutProtoErr", 575 | "viewCloseError", 576 | "viewERR", 577 | "viewERROR", 578 | "viewError", 579 | } { 580 | if isValidErrorVarName(tt) { 581 | t.Errorf("%q must be invalid error var name", tt) 582 | } 583 | } 584 | } 585 | 586 | func Test_isInitialism(t *testing.T) { 587 | for _, tt := range []struct { 588 | in string 589 | expected bool 590 | }{ 591 | {in: "E", expected: true}, 592 | {in: "e", expected: true}, 593 | {in: "EOF", expected: true}, 594 | {in: "eof", expected: true}, 595 | {in: "ERPCMISMATCH", expected: true}, 596 | {in: "EndOfFile", expected: false}, 597 | {in: "withStackError", expected: false}, 598 | {in: "badData", expected: false}, 599 | } { 600 | t.Run(tt.in, func(t *testing.T) { 601 | if isInitialism(tt.in) != tt.expected { 602 | t.Fail() 603 | } 604 | }) 605 | } 606 | } 607 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/src/regular/error_types.go: -------------------------------------------------------------------------------- 1 | package regular 2 | 3 | type NotErrorType struct{} 4 | 5 | func (t NotErrorType) Set() {} 6 | func (t NotErrorType) Get() {} 7 | 8 | type DNSConfigError struct{} 9 | 10 | func (D DNSConfigError) Error() string { return "DNS config error" } 11 | 12 | type someTypeWithoutPtr struct{} // want "the error type name `someTypeWithoutPtr` should conform to the `xxxError` format" 13 | func (s someTypeWithoutPtr) Error() string { return "someTypeWithoutPtr" } 14 | 15 | type SomeTypeWithoutPtr struct{} // want "the error type name `SomeTypeWithoutPtr` should conform to the `XxxError` format" 16 | func (s SomeTypeWithoutPtr) Error() string { return "SomeTypeWithoutPtr" } 17 | 18 | type someTypeWithPtr struct{} // want "the error type name `someTypeWithPtr` should conform to the `xxxError` format" 19 | func (s *someTypeWithPtr) Error() string { return "someTypeWithPtr" } 20 | 21 | type ( 22 | SomeTypeAlias = SomeTypeWithPtr // want "the error type name `SomeTypeAlias` should conform to the `XxxError` format" 23 | 24 | SomeTypeWithPtr struct{} // want "the error type name `SomeTypeWithPtr` should conform to the `XxxError` format" 25 | ) 26 | 27 | func (s *SomeTypeWithPtr) Error() string { return "SomeTypeWithPtr" } 28 | 29 | type timeoutErr struct { // want "the error type name `timeoutErr` should conform to the `xxxError` format" 30 | error 31 | } 32 | 33 | type DeadlineErr struct { // want "the error type name `DeadlineErr` should conform to the `XxxError` format" 34 | timeoutErr 35 | } 36 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/src/regular/error_types_arrays.go: -------------------------------------------------------------------------------- 1 | package regular 2 | 3 | import "strings" 4 | 5 | type ValidationErrors []string 6 | 7 | func (ve ValidationErrors) Error() string { return strings.Join(ve, "\n") } 8 | 9 | type validationErrors []string 10 | 11 | func (ve validationErrors) Error() string { return strings.Join(ve, "\n") } 12 | 13 | type TenErrors [10]string 14 | 15 | func (te TenErrors) Error() string { return strings.Join(te[:], "\n") } 16 | 17 | type tenErrors [10]string 18 | 19 | func (te tenErrors) Error() string { return strings.Join(te[:], "\n") } 20 | 21 | type MultiErr []error // want "the error type name `MultiErr` should conform to the `XxxErrors` or `XxxError` format" 22 | func (me MultiErr) Error() string { return "" } 23 | 24 | type multiErr []error // want "the error type name `multiErr` should conform to the `xxxErrors` or `xxxError` format" 25 | func (me multiErr) Error() string { return "" } 26 | 27 | type Twoerr [2]error // want "the error type name `Twoerr` should conform to the `XxxErrors` or `XxxError` format" 28 | func (te Twoerr) Error() string { return te[0].Error() + "\n" + te[1].Error() } 29 | 30 | type twoErrorss [2]error // want "the error type name `twoErrorss` should conform to the `xxxErrors` or `xxxError` format" 31 | func (te twoErrorss) Error() string { return te[0].Error() + "\n" + te[1].Error() } 32 | 33 | type MultiError []error 34 | 35 | func (me MultiError) Error() string { return "" } 36 | 37 | type multiError []error 38 | 39 | func (me multiError) Error() string { return "" } 40 | 41 | type TwoError [2]error 42 | 43 | func (te TwoError) Error() string { return te[0].Error() + "\n" + te[1].Error() } 44 | 45 | type twoError [2]error 46 | 47 | func (te twoError) Error() string { return te[0].Error() + "\n" + te[1].Error() } 48 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/src/regular/sentinel.go: -------------------------------------------------------------------------------- 1 | package regular 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var ( 9 | EOF = errors.New("end of file") 10 | ErrEndOfFile = errors.New("end of file") 11 | errEndOfFile = errors.New("end of file") 12 | 13 | EndOfFileError = errors.New("end of file") // want "the sentinel error name `EndOfFileError` should conform to the `ErrXxx` format" 14 | ErrorEndOfFile = errors.New("end of file") // want "the sentinel error name `ErrorEndOfFile` should conform to the `ErrXxx` format" 15 | EndOfFileErr = errors.New("end of file") // want "the sentinel error name `EndOfFileErr` should conform to the `ErrXxx` format" 16 | endOfFileError = errors.New("end of file") // want "the sentinel error name `endOfFileError` should conform to the `errXxx` format" 17 | errorEndOfFile = errors.New("end of file") // want "the sentinel error name `errorEndOfFile` should conform to the `errXxx` format" 18 | ) 19 | 20 | const maxSize = 256 21 | 22 | var ( 23 | ErrOutOfSize = fmt.Errorf("out of size (max %d)", maxSize) 24 | errOutOfSize = fmt.Errorf("out of size (max %d)", maxSize) 25 | 26 | OutOfSizeError = fmt.Errorf("out of size (max %d)", maxSize) // want "the sentinel error name `OutOfSizeError` should conform to the `ErrXxx` format" 27 | outOfSizeError = fmt.Errorf("out of size (max %d)", maxSize) // want "the sentinel error name `outOfSizeError` should conform to the `errXxx` format" 28 | ) 29 | 30 | func errInsideFuncIsNotSentinel() error { 31 | var lastErr error 32 | return lastErr 33 | } 34 | 35 | var _ = func() { 36 | run("", func() { 37 | var orderErr error 38 | _ = orderErr 39 | }) 40 | } 41 | 42 | func run(_ string, _ func()) {} 43 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/src/regular/sentinel_from_constants.go: -------------------------------------------------------------------------------- 1 | package regular 2 | 3 | type constError string 4 | 5 | func (e constError) Error() string { 6 | return string(e) 7 | } 8 | 9 | const ( 10 | ErrTooManyErrors constError = "too many errors found" 11 | 12 | ErrorTooMany1 constError = "too many errors found" // want "the sentinel error name `ErrorTooMany1` should conform to the `ErrXxx` format" 13 | ErrorTooMany2 = constError("too many errors found") // want "the sentinel error name `ErrorTooMany2` should conform to the `ErrXxx` format" 14 | ) 15 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/src/regular/sentinel_from_funcs.go: -------------------------------------------------------------------------------- 1 | package regular 2 | 3 | import "io" 4 | 5 | var ( 6 | ErrA = newSomeTypeWithPtr() 7 | ErrB = newSomeTypeWithPtr2() 8 | ErrC = newSomeTypeWithoutPtr() 9 | ErrD = newSomeTypeWithoutPtr2() 10 | ErrE = new(SomeTypeWithPtr) 11 | ErrF = &SomeTypeWithPtr{} 12 | ErrG = SomeTypeWithoutPtr{} 13 | 14 | AErr = newSomeTypeWithPtr() // want "the sentinel error name `AErr` should conform to the `ErrXxx` format" 15 | BErr = newSomeTypeWithPtr2() // want "the sentinel error name `BErr` should conform to the `ErrXxx` format" 16 | CErr = newSomeTypeWithoutPtr() // want "the sentinel error name `CErr` should conform to the `ErrXxx` format" 17 | DErr = newSomeTypeWithoutPtr2() // want "the sentinel error name `DErr` should conform to the `ErrXxx` format" 18 | EErr = new(SomeTypeWithPtr) // want "the sentinel error name `EErr` should conform to the `ErrXxx` format" 19 | FErr = &SomeTypeWithPtr{} // want "the sentinel error name `FErr` should conform to the `ErrXxx` format" 20 | GErr = SomeTypeWithoutPtr{} // want "the sentinel error name `GErr` should conform to the `ErrXxx` format" 21 | 22 | AErrr error = newSomeTypeWithPtr2() // want "the sentinel error name `AErrr` should conform to the `ErrXxx` format" 23 | BErrr error = newSomeTypeWithoutPtr() // want "the sentinel error name `BErrr` should conform to the `ErrXxx` format" 24 | CErrr error = new(SomeTypeWithPtr) // want "the sentinel error name `CErrr` should conform to the `ErrXxx` format" 25 | DErrr error = &SomeTypeWithPtr{} // want "the sentinel error name `DErrr` should conform to the `ErrXxx` format" 26 | EErrr error = SomeTypeWithoutPtr{} // want "the sentinel error name `EErrr` should conform to the `ErrXxx` format" 27 | 28 | ErrByAnonymousFunc = func() error { return nil } 29 | ByAnonymousFuncErr = func() error { return io.EOF }() // want "the sentinel error name `ByAnonymousFuncErr` should conform to the `ErrXxx` format" 30 | ) 31 | 32 | var ( 33 | InitializedLaterError error // want "the sentinel error name `InitializedLaterError` should conform to the `ErrXxx` format" 34 | InitializedLaterImplicitError SomeTypeWithoutPtr // want "the sentinel error name `InitializedLaterImplicitError` should conform to the `ErrXxx` format" 35 | InitializedLaterImplicitPtrError *SomeTypeWithPtr // want "the sentinel error name `InitializedLaterImplicitPtrError` should conform to the `ErrXxx` format" 36 | ) 37 | 38 | func init() { 39 | InitializedLaterError = newSomeTypeWithPtr() 40 | InitializedLaterImplicitError = newSomeTypeWithoutPtr() 41 | InitializedLaterImplicitPtrError = newSomeTypeWithPtr2() 42 | } 43 | 44 | func newSomeTypeWithPtr() error { 45 | return new(SomeTypeWithPtr) 46 | } 47 | 48 | func newSomeTypeWithPtr2() *SomeTypeWithPtr { 49 | return nil 50 | } 51 | 52 | func newSomeTypeWithoutPtr() SomeTypeWithoutPtr { 53 | return SomeTypeWithoutPtr{} 54 | } 55 | 56 | func newSomeTypeWithoutPtr2() error { 57 | return SomeTypeWithoutPtr{} 58 | } 59 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/src/regular/sentinel_from_pkg_alias.go: -------------------------------------------------------------------------------- 1 | package regular 2 | 3 | import ( 4 | stderrors "errors" 5 | stdfmt "fmt" 6 | ) 7 | 8 | var ErrAlias = stderrors.New("err from alias") 9 | var AliasErr = stderrors.New("err from alias") // want "the sentinel error name `AliasErr` should conform to the `ErrXxx` format" 10 | 11 | var ErrOutOfSize2 = stdfmt.Errorf("out of size (max %d)", maxSize) 12 | var OutOfSizeError2 = stdfmt.Errorf("out of size (max %d)", maxSize) // want "the sentinel error name `OutOfSizeError2` should conform to the `ErrXxx` format" 13 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/src/regular/sentinel_from_types.go: -------------------------------------------------------------------------------- 1 | package regular 2 | 3 | import "net" 4 | 5 | var ( 6 | InvalidAddrError = new(net.AddrError) // want "the sentinel error name `InvalidAddrError` should conform to the `ErrXxx` format" 7 | InvalidAddrErr error = new(net.AddrError) // want "the sentinel error name `InvalidAddrErr` should conform to the `ErrXxx` format" 8 | NotErr = new(NotErrorType) 9 | 10 | Aa = new(someTypeWithPtr) // want "the sentinel error name `Aa` should conform to the `ErrXxx` format" 11 | Bb = someTypeWithoutPtr{} // want "the sentinel error name `Bb` should conform to the `ErrXxx` format" 12 | Bbb = someTypeWithPtr{} 13 | 14 | cC error = new(someTypeWithPtr) // want "the sentinel error name `cC` should conform to the `errXxx` format" 15 | dD error = someTypeWithoutPtr{} // want "the sentinel error name `dD` should conform to the `errXxx` format" 16 | 17 | Alias = InvalidAddrErr // want "the sentinel error name `Alias` should conform to the `ErrXxx` format" 18 | ) 19 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/src/unusual/errortype/user_error_type.go: -------------------------------------------------------------------------------- 1 | package errortype 2 | 3 | type error string 4 | 5 | var EndOfFileError = error("I am not error") 6 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/src/unusual/generics/generic_types.go: -------------------------------------------------------------------------------- 1 | package generics 2 | 3 | type NotErrorGeneric[T float64 | int] struct { 4 | Limit T 5 | } 6 | 7 | type SomeError[T ~string] struct{ Code T } 8 | 9 | func (e SomeError[T]) Error() string { return string(e.Code) } 10 | 11 | type SomePtrError[T ~string] struct{ Code T } 12 | 13 | func (e *SomePtrError[T]) Error() string { return string(e.Code) } 14 | 15 | type someErr[T ~string] struct{ Code T } // want "the error type name `someErr` should conform to the `xxxError` format" 16 | func (e someErr[T]) Error() string { return string(e.Code) } 17 | 18 | type SomePtrErr[T ~string] struct{ Code T } // want "the error type name `SomePtrErr` should conform to the `XxxError` format" 19 | func (e *SomePtrErr[T]) Error() string { return string(e.Code) } 20 | 21 | var ( 22 | ErrOK = &SomePtrError[string]{Code: "200 OK"} 23 | okErr = &SomePtrError[string]{Code: "200 OK"} // want "the sentinel error name `okErr` should conform to the `errXxx` format" 24 | 25 | ErrNotFound = SomeError[string]{Code: "Not Found"} 26 | NotFoundErr = SomeError[string]{Code: "Not Found"} // want "the sentinel error name `NotFoundErr` should conform to the `ErrXxx` format" 27 | 28 | statusCodeError = new(SomePtrError[string]) // want "the sentinel error name `statusCodeError` should conform to the `errXxx` format" 29 | ExplicitError error = new(SomePtrError[string]) // want "the sentinel error name `ExplicitError` should conform to the `ErrXxx` format" 30 | ) 31 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/src/unusual/generics/issue24.go: -------------------------------------------------------------------------------- 1 | package generics 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "reflect" 8 | ) 9 | 10 | type ( 11 | Req any 12 | Resp any 13 | ) 14 | 15 | type timeoutErr[REQ Req, RESP Resp] struct { // want "the error type name `timeoutErr` should conform to the `xxxError` format" 16 | err error 17 | sending bool 18 | } 19 | 20 | func (e *timeoutErr[REQ, RESP]) Error() string { 21 | var req REQ 22 | var resp RESP 23 | 24 | direction := "sending" 25 | if !e.sending { 26 | direction = "receiving" 27 | } 28 | 29 | return fmt.Sprintf("deferred call %T->%T timeout %s: %s", 30 | reflect.TypeOf(req), reflect.TypeOf(resp), direction, e.err.Error()) 31 | } 32 | 33 | func (e *timeoutErr[REQ, RESP]) Unwrap() error { 34 | return e.err 35 | } 36 | 37 | type TimeoutError[REQ Req, RESP Resp] struct{} // 38 | func (TimeoutError[REQ, RESP]) Error() string { return "timeouted" } 39 | 40 | type ValErr[A, B, C, D, E, F any] struct{} // want "the error type name `ValErr` should conform to the `XxxError` format" 41 | func (ValErr[A, B, C, D, E, F]) Error() string { return "boom!" } 42 | 43 | var ( 44 | ErrTimeout error = &timeoutErr[*http.Request, *http.Response]{err: context.DeadlineExceeded, sending: false} 45 | tErr error = &timeoutErr[string, string]{err: context.DeadlineExceeded, sending: true} // want "the sentinel error name `tErr` should conform to the `errXxx` format" 46 | ) 47 | -------------------------------------------------------------------------------- /pkg/analyzer/testdata/src/unusual/newfunc/user_new_func.go: -------------------------------------------------------------------------------- 1 | package newfunc 2 | 3 | var FromUserNewError = new() // want "the sentinel error name `FromUserNewError` should conform to the `ErrXxx` format" 4 | 5 | func new() error { 6 | return nil 7 | } 8 | --------------------------------------------------------------------------------