Skip to content

Commit d64847f

Browse files
committed
Permit comparisons in (*t).Is(error) bool functions (fixes #11)
1 parent e319edb commit d64847f

File tree

3 files changed

+81
-2
lines changed

3 files changed

+81
-2
lines changed

errorlint/analysis.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
5757
type TypesInfoExt struct {
5858
types.Info
5959

60-
// Maps AST nodes back to the node they are contain within.
60+
// Maps AST nodes back to the node they are contained within.
6161
NodeParent map[ast.Node]ast.Node
6262

6363
// Maps an object back to all identifiers to refer to it.
@@ -97,3 +97,16 @@ func newTypesInfoExt(info *types.Info) *TypesInfoExt {
9797
IdentifiersForObject: identifiersForObject,
9898
}
9999
}
100+
101+
func (info *TypesInfoExt) ContainingFuncDecl(node ast.Node) *ast.FuncDecl {
102+
for parent := info.NodeParent[node]; ; parent = info.NodeParent[parent] {
103+
if _, ok := parent.(*ast.File); ok {
104+
break
105+
}
106+
if fun, ok := parent.(*ast.FuncDecl); ok {
107+
return fun
108+
break
109+
}
110+
}
111+
return nil
112+
}

errorlint/lint.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,14 @@ func LintErrorComparisons(fset *token.FileSet, info *TypesInfoExt) []Lint {
156156
if !isErrorComparison(info.Info, binExpr) {
157157
continue
158158
}
159-
159+
// Some errors that are returned from some functions are exempt.
160160
if isAllowedErrorComparison(info, binExpr) {
161161
continue
162162
}
163+
// Comparisons that happen in `func (type) Is(error) bool` are okay.
164+
if isNodeInErrorIsFunc(info, binExpr) {
165+
continue
166+
}
163167

164168
lints = append(lints, Lint{
165169
Message: fmt.Sprintf("comparing with %s will fail on wrapped errors. Use errors.Is to check for a specific error", binExpr.Op),
@@ -181,6 +185,9 @@ func LintErrorComparisons(fset *token.FileSet, info *TypesInfoExt) []Lint {
181185
if tagType.Type.String() != "error" {
182186
continue
183187
}
188+
if isNodeInErrorIsFunc(info, switchStmt) {
189+
continue
190+
}
184191

185192
lints = append(lints, Lint{
186193
Message: "switch on an error will fail on wrapped errors. Use errors.Is to check for specific errors",
@@ -207,6 +214,30 @@ func isErrorComparison(info types.Info, binExpr *ast.BinaryExpr) bool {
207214
return tx.Type.String() == "error" || ty.Type.String() == "error"
208215
}
209216

217+
func isNodeInErrorIsFunc(info *TypesInfoExt, node ast.Node) bool {
218+
funcDecl := info.ContainingFuncDecl(node)
219+
if funcDecl == nil {
220+
return false
221+
}
222+
223+
if funcDecl.Name.Name != "Is" {
224+
return false
225+
}
226+
if funcDecl.Recv == nil {
227+
return false
228+
}
229+
// There should be 1 argument of type error.
230+
if ii := funcDecl.Type.Params.List; len(ii) != 1 || info.Types[ii[0].Type].Type.String() != "error" {
231+
return false
232+
}
233+
// The return type should be bool.
234+
if ii := funcDecl.Type.Results.List; len(ii) != 1 || info.Types[ii[0].Type].Type.String() != "bool" {
235+
return false
236+
}
237+
238+
return true
239+
}
240+
210241
func LintErrorTypeAssertions(fset *token.FileSet, info types.Info) []Lint {
211242
lints := []Lint{}
212243

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package issues
2+
3+
import (
4+
"errors"
5+
"os"
6+
"syscall"
7+
)
8+
9+
// Regression test for: https://github.com/polyfloyd/go-errorlint/issues/11
10+
11+
var ErrInvalidConfig = errors.New("invalid configuration")
12+
13+
// invalidConfig type is needed to make any error returned from Validator
14+
// to be ErrInvalidConfig.
15+
type invalidConfig struct {
16+
err error
17+
}
18+
19+
func (e *invalidConfig) Is(target error) bool {
20+
return target == ErrInvalidConfig
21+
}
22+
23+
type Errno uintptr
24+
25+
func (e Errno) Is(target error) bool {
26+
switch target {
27+
case os.ErrPermission:
28+
return e == Errno(syscall.EACCES) || e == Errno(syscall.EPERM)
29+
case os.ErrExist:
30+
return e == Errno(syscall.EEXIST) || e == Errno(syscall.ENOTEMPTY)
31+
case os.ErrNotExist:
32+
return e == Errno(syscall.ENOENT)
33+
}
34+
return false
35+
}

0 commit comments

Comments
 (0)