Skip to content

Commit

Permalink
introduce helpers_*.go files (#92)
Browse files Browse the repository at this point in the history
* introduce helpers_*.go files

* cosmetis
  • Loading branch information
Antonboom authored May 18, 2024
1 parent bff320f commit a6e65e3
Show file tree
Hide file tree
Showing 31 changed files with 647 additions and 569 deletions.
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ Remember that [assert.TestingT](https://pkg.go.dev/github.com/stretchr/testify/a
[require.TestingT](https://pkg.go.dev/github.com/stretchr/testify/require#TestingT) are different interfaces,
which may be important in some contexts.

Also, pay attention to `internal/checkers/helpers_*.go` files. Try to reuse existing code as much as possible.

### 8) Improve tests from p.4 if necessary

Pay attention to `Assertion` and `NewAssertionExpander`, but keep your tests as small as possible.
Expand Down
92 changes: 0 additions & 92 deletions internal/checkers/bool_compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package checkers
import (
"go/ast"
"go/token"
"go/types"

"golang.org/x/tools/go/analysis"

Expand Down Expand Up @@ -204,101 +203,10 @@ func (checker BoolCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis.
return nil
}

func isEmptyInterface(pass *analysis.Pass, expr ast.Expr) bool {
t, ok := pass.TypesInfo.Types[expr]
if !ok {
return false
}

iface, ok := t.Type.Underlying().(*types.Interface)
return ok && iface.NumMethods() == 0
}

func isBuiltinBool(pass *analysis.Pass, e ast.Expr) bool {
basicType, ok := pass.TypesInfo.TypeOf(e).(*types.Basic)
return ok && basicType.Kind() == types.Bool
}

func isBoolOverride(pass *analysis.Pass, e ast.Expr) bool {
namedType, ok := pass.TypesInfo.TypeOf(e).(*types.Named)
return ok && namedType.Obj().Name() == "bool"
}

var (
falseObj = types.Universe.Lookup("false")
trueObj = types.Universe.Lookup("true")
)

func isUntypedTrue(pass *analysis.Pass, e ast.Expr) bool {
return analysisutil.IsObj(pass.TypesInfo, e, trueObj)
}

func isUntypedFalse(pass *analysis.Pass, e ast.Expr) bool {
return analysisutil.IsObj(pass.TypesInfo, e, falseObj)
}

func isComparisonWithTrue(pass *analysis.Pass, e ast.Expr, op token.Token) (ast.Expr, bool) {
return isComparisonWith(pass, e, isUntypedTrue, op)
}

func isComparisonWithFalse(pass *analysis.Pass, e ast.Expr, op token.Token) (ast.Expr, bool) {
return isComparisonWith(pass, e, isUntypedFalse, op)
}

type predicate func(pass *analysis.Pass, e ast.Expr) bool

func isComparisonWith(pass *analysis.Pass, e ast.Expr, predicate predicate, op token.Token) (ast.Expr, bool) {
be, ok := e.(*ast.BinaryExpr)
if !ok {
return nil, false
}
if be.Op != op {
return nil, false
}

t1, t2 := predicate(pass, be.X), predicate(pass, be.Y)
if xor(t1, t2) {
if t1 {
return be.Y, true
}
return be.X, true
}
return nil, false
}

func isNegation(e ast.Expr) (ast.Expr, bool) {
ue, ok := e.(*ast.UnaryExpr)
if !ok {
return nil, false
}
return ue.X, ue.Op == token.NOT
}

func xor(a, b bool) bool {
return a != b
}

// anyVal returns the first value[i] for which bools[i] is true.
func anyVal[T any](bools []bool, vals ...T) (T, bool) {
if len(bools) != len(vals) {
panic("inconsistent usage of valOr") //nolint:forbidigo // Does not depend on the code being analyzed.
}

for i, b := range bools {
if b {
return vals[i], true
}
}

var _default T
return _default, false
}

func anyCondSatisfaction(pass *analysis.Pass, p predicate, vals ...ast.Expr) bool {
for _, v := range vals {
if p(pass, v) {
return true
}
}
return false
}
22 changes: 0 additions & 22 deletions internal/checkers/call_meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,25 +119,3 @@ func trimTArg(pass *analysis.Pass, args []ast.Expr) []ast.Expr {
}
return args
}

func implementsTestingT(pass *analysis.Pass, arg ast.Expr) bool {
return implementsAssertTestingT(pass, arg) || implementsRequireTestingT(pass, arg)
}

func implementsAssertTestingT(pass *analysis.Pass, e ast.Expr) bool {
assertTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.AssertPkgPath, "TestingT")
return (assertTestingTObj != nil) && implements(pass, e, assertTestingTObj)
}

func implementsRequireTestingT(pass *analysis.Pass, e ast.Expr) bool {
requireTestingTObj := analysisutil.ObjectOf(pass.Pkg, testify.RequirePkgPath, "TestingT")
return (requireTestingTObj != nil) && implements(pass, e, requireTestingTObj)
}

func implements(pass *analysis.Pass, e ast.Expr, ifaceObj types.Object) bool {
t := pass.TypesInfo.TypeOf(e)
if t == nil {
return false
}
return types.Implements(t, ifaceObj.Type().Underlying().(*types.Interface))
}
11 changes: 0 additions & 11 deletions internal/checkers/compares.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package checkers

import (
"bytes"
"go/ast"
"go/token"

"golang.org/x/tools/go/analysis"

"github.com/Antonboom/testifylint/internal/analysisutil"
)

// Compares detects situations like
Expand Down Expand Up @@ -86,11 +83,3 @@ var tokenToProposedFnInsteadOfFalse = map[token.Token]string{
token.LSS: "GreaterOrEqual",
token.LEQ: "Greater",
}

// formatAsCallArgs joins a and b and return bytes like `a, b`.
func formatAsCallArgs(pass *analysis.Pass, a, b ast.Node) []byte {
return bytes.Join([][]byte{
analysisutil.NodeBytes(pass.Fset, a),
analysisutil.NodeBytes(pass.Fset, b),
}, []byte(", "))
}
34 changes: 0 additions & 34 deletions internal/checkers/empty.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package checkers

import (
"fmt"
"go/ast"
"go/token"
"go/types"

"golang.org/x/tools/go/analysis"

Expand Down Expand Up @@ -138,35 +136,3 @@ func (checker Empty) checkNotEmpty(pass *analysis.Pass, call *CallMeta) *analysi
}
return nil
}

var lenObj = types.Universe.Lookup("len")

func isLenCallAndZero(pass *analysis.Pass, a, b ast.Expr) (ast.Expr, bool) {
lenArg, ok := isBuiltinLenCall(pass, a)
return lenArg, ok && isZero(b)
}

func isBuiltinLenCall(pass *analysis.Pass, e ast.Expr) (ast.Expr, bool) {
ce, ok := e.(*ast.CallExpr)
if !ok {
return nil, false
}

if analysisutil.IsObj(pass.TypesInfo, ce.Fun, lenObj) && len(ce.Args) == 1 {
return ce.Args[0], true
}
return nil, false
}

func isZero(e ast.Expr) bool {
return isIntNumber(e, 0)
}

func isOne(e ast.Expr) bool {
return isIntNumber(e, 1)
}

func isIntNumber(e ast.Expr, v int) bool {
bl, ok := e.(*ast.BasicLit)
return ok && bl.Kind == token.INT && bl.Value == fmt.Sprintf("%d", v)
}
24 changes: 0 additions & 24 deletions internal/checkers/error_is_as.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"go/types"

"golang.org/x/tools/go/analysis"

"github.com/Antonboom/testifylint/internal/analysisutil"
)

// ErrorIsAs detects situations like
Expand Down Expand Up @@ -142,25 +140,3 @@ func (checker ErrorIsAs) Check(pass *analysis.Pass, call *CallMeta) *analysis.Di
}
return nil
}

func isErrorsIsCall(pass *analysis.Pass, ce *ast.CallExpr) bool {
return isErrorsPkgFnCall(pass, ce, "Is")
}

func isErrorsAsCall(pass *analysis.Pass, ce *ast.CallExpr) bool {
return isErrorsPkgFnCall(pass, ce, "As")
}

func isErrorsPkgFnCall(pass *analysis.Pass, ce *ast.CallExpr, fn string) bool {
se, ok := ce.Fun.(*ast.SelectorExpr)
if !ok {
return false
}

errorsIsObj := analysisutil.ObjectOf(pass.Pkg, "errors", fn)
if errorsIsObj == nil {
return false
}

return analysisutil.IsObj(pass.TypesInfo, se.Sel, errorsIsObj)
}
21 changes: 0 additions & 21 deletions internal/checkers/error_nil.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package checkers
import (
"go/ast"
"go/token"
"go/types"

"golang.org/x/tools/go/analysis"

Expand Down Expand Up @@ -91,23 +90,3 @@ func (checker ErrorNil) Check(pass *analysis.Pass, call *CallMeta) *analysis.Dia
}
return nil
}

var (
errorType = types.Universe.Lookup("error").Type()
errorIface = errorType.Underlying().(*types.Interface)
)

func isError(pass *analysis.Pass, expr ast.Expr) bool {
t := pass.TypesInfo.TypeOf(expr)
if t == nil {
return false
}

_, ok := t.Underlying().(*types.Interface)
return ok && types.Implements(t, errorIface)
}

func isNil(expr ast.Expr) bool {
ident, ok := expr.(*ast.Ident)
return ok && ident.Name == "nil"
}
36 changes: 0 additions & 36 deletions internal/checkers/expected_actual.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package checkers
import (
"go/ast"
"go/token"
"go/types"
"regexp"

"golang.org/x/tools/go/analysis"
Expand Down Expand Up @@ -178,38 +177,3 @@ func isExpectedValueFactory(pass *analysis.Pass, ce *ast.CallExpr, pattern *rege
}
return false
}

func isBasicLit(e ast.Expr) bool {
_, ok := e.(*ast.BasicLit)
return ok
}

func isUntypedConst(p *analysis.Pass, e ast.Expr) bool {
t := p.TypesInfo.TypeOf(e)
if t == nil {
return false
}

b, ok := t.(*types.Basic)
return ok && b.Info()&types.IsUntyped > 0
}

func isTypedConst(p *analysis.Pass, e ast.Expr) bool {
tt, ok := p.TypesInfo.Types[e]
return ok && tt.IsValue() && tt.Value != nil
}

func isIdentNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool {
id, ok := e.(*ast.Ident)
return ok && pattern.MatchString(id.Name)
}

func isStructVarNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool {
s, ok := e.(*ast.SelectorExpr)
return ok && isIdentNamedAsExpected(pattern, s.X)
}

func isStructFieldNamedAsExpected(pattern *regexp.Regexp, e ast.Expr) bool {
s, ok := e.(*ast.SelectorExpr)
return ok && isIdentNamedAsExpected(pattern, s.Sel)
}
24 changes: 2 additions & 22 deletions internal/checkers/float_compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package checkers

import (
"fmt"
"go/ast"
"go/token"
"go/types"

"golang.org/x/tools/go/analysis"
)
Expand Down Expand Up @@ -33,10 +31,10 @@ func (checker FloatCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis
return len(call.Args) > 1 && (isFloat(pass, call.Args[0]) || isFloat(pass, call.Args[1]))

case "True":
return len(call.Args) > 0 && isFloatCompare(pass, call.Args[0], token.EQL)
return len(call.Args) > 0 && isComparisonWithFloat(pass, call.Args[0], token.EQL)

case "False":
return len(call.Args) > 0 && isFloatCompare(pass, call.Args[0], token.NEQ)
return len(call.Args) > 0 && isComparisonWithFloat(pass, call.Args[0], token.NEQ)
}
return false
}()
Expand All @@ -50,21 +48,3 @@ func (checker FloatCompare) Check(pass *analysis.Pass, call *CallMeta) *analysis
}
return nil
}

func isFloat(pass *analysis.Pass, expr ast.Expr) bool {
t := pass.TypesInfo.TypeOf(expr)
if t == nil {
return false
}

bt, ok := t.Underlying().(*types.Basic)
return ok && (bt.Info()&types.IsFloat > 0)
}

func isFloatCompare(p *analysis.Pass, e ast.Expr, op token.Token) bool {
be, ok := e.(*ast.BinaryExpr)
if !ok {
return false
}
return be.Op == op && (isFloat(p, be.X) || isFloat(p, be.Y))
}
Loading

0 comments on commit a6e65e3

Please sign in to comment.