Skip to content

Commit

Permalink
Merge branch 'main' into go-1.22
Browse files Browse the repository at this point in the history
  • Loading branch information
butuzov committed Jan 23, 2024
2 parents 58a4688 + 12025d7 commit cc55cb0
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 106 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ jobs:
fail-fast: true
matrix:
go:
- "1.18"
- "1.19"
- "1.20"
- "1.21"
- "1.22"
Expand All @@ -29,7 +31,7 @@ jobs:
with:
go-version: ${{ matrix.go }}

- uses: actions/cache@v3
- uses: actions/cache@v4
if: ${{ !env.ACT }}
with:
path: ~/go/pkg/mod
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
with:
go-version: ${{ env.GO_VERSION }}

- uses: actions/cache@v3
- uses: actions/cache@v4
if: ${{ !env.ACT }}
with:
path: ~/go/pkg/mod
Expand Down
59 changes: 43 additions & 16 deletions analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"flag"
"go/ast"
gotypes "go/types"
"runtime"
"strings"
"sync"

Expand Down Expand Up @@ -70,7 +71,6 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) {

// 004. Filtering Results.
for _, issue := range filterInterfaces(pass, f.Type, dotImportedStd) {

if a.handler.IsValid(issue) {
continue
}
Expand Down Expand Up @@ -146,13 +146,10 @@ func filterInterfaces(p *analysis.Pass, ft *ast.FuncType, di map[string]struct{}
return results
}

tp := newTypeParams(ft.TypeParams)

for _, el := range ft.Results.List {
switch v := el.Type.(type) {
// ----- empty or anonymous interfaces
case *ast.InterfaceType:

if len(v.Methods.List) == 0 {
results = append(results, types.NewIssue("interface{}", types.EmptyInterface))
continue
Expand All @@ -164,35 +161,65 @@ func filterInterfaces(p *analysis.Pass, ft *ast.FuncType, di map[string]struct{}
case *ast.Ident:

t1 := p.TypesInfo.TypeOf(el.Type)
if !gotypes.IsInterface(t1.Underlying()) {
val, ok := t1.Underlying().(*gotypes.Interface)
if !ok {
continue
}

word := t1.String()
// only build in interface is error
if obj := gotypes.Universe.Lookup(word); obj != nil {
results = append(results, types.NewIssue(obj.Name(), types.ErrorInterface))
var (
name = t1.String()
isNamed = strings.Contains(name, ".")
isEmpty = val.Empty()
)

// catching any
if isEmpty && name == "any" {
results = append(results, types.NewIssue(name, types.EmptyInterface))
continue
}

// NOTE: FIXED!
if name == "error" {
results = append(results, types.NewIssue(name, types.ErrorInterface))
continue
}

// found in type params
if tp.In(word) {
results = append(results, types.NewIssue(word, types.Generic))
if !isNamed {

typeParams := val.String()
prefix, suffix := "interface{", "}"
if strings.HasPrefix(typeParams, prefix) { // nolint: gosimple
typeParams = typeParams[len(prefix):]
}
if strings.HasSuffix(typeParams, suffix) {
typeParams = typeParams[:len(typeParams)-1]
}

goVersion := runtime.Version()
if strings.HasPrefix(goVersion, "go1.18") || strings.HasPrefix(goVersion, "go1.19") {
typeParams = strings.ReplaceAll(typeParams, "|", " | ")
}

results = append(results, types.IFace{
Name: name,
Type: types.Generic,
OfType: typeParams,
})
continue
}

// is it dot-imported package?
// handling cases when stdlib package imported via "." dot-import
if len(di) > 0 {
name := stdPkgInterface(word)
if _, ok := di[name]; ok {
results = append(results, types.NewIssue(word, types.NamedStdInterface))
pkgName := stdPkgInterface(name)
if _, ok := di[pkgName]; ok {
results = append(results, types.NewIssue(name, types.NamedStdInterface))

continue
}
}

results = append(results, types.NewIssue(word, types.NamedInterface))
results = append(results, types.NewIssue(name, types.NamedInterface))

// ------- standard library and 3rd party interfaces
case *ast.SelectorExpr:
Expand Down
74 changes: 41 additions & 33 deletions analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ func TestAll(t *testing.T) {
"New returns interface (github.com/foo/bar.Buzzer)",
"NewDeclared returns interface (internal/sample.Doer)",
"newIDoer returns interface (example.iDoer)",
"newIDoerAny returns interface (example.iDoerAny)",
"NewNamedStruct returns interface (example.FooerBarer)",
},
})
Expand Down Expand Up @@ -167,6 +168,7 @@ func TestAll(t *testing.T) {
"New returns interface (github.com/foo/bar.Buzzer)",
"NewDeclared returns interface (internal/sample.Doer)",
"newIDoer returns interface (example.iDoer)",
"newIDoerAny returns interface (example.iDoerAny)",
"NewNamedStruct returns interface (example.FooerBarer)",
},
})
Expand All @@ -180,6 +182,7 @@ func TestAll(t *testing.T) {
want: []string{
"NewDeclared returns interface (internal/sample.Doer)",
"newIDoer returns interface (example.iDoer)",
"newIDoerAny returns interface (example.iDoerAny)",
"NewNamedStruct returns interface (example.FooerBarer)",
"NamedContext returns interface (context.Context)",
"NamedBytes returns interface (io.Writer)",
Expand Down Expand Up @@ -210,19 +213,6 @@ func TestAll(t *testing.T) {
},
})

// was already commented
// tests = append(tests, testCase{
// name: "Named/allow/(stdmimic)",
// mask: []string{"internal/io/*"},
// meta: map[string]string{
// "allow": "", //
// },
// pkgm: "io",
// want: []string{
// "Get returns interface (io.Writer)",
// },
// })

tests = append(tests, testCase{
name: "Named Interfaces/regexp/allow",
mask: []string{"named_*.go", "github.com/foo/bar/*", "internal/sample/*"},
Expand All @@ -233,6 +223,7 @@ func TestAll(t *testing.T) {
"s returns interface (github.com/foo/bar.Buzzer)",
"New returns interface (github.com/foo/bar.Buzzer)",
"newIDoer returns interface (example.iDoer)",
"newIDoerAny returns interface (example.iDoerAny)",
"NewNamedStruct returns interface (example.FooerBarer)",
"NamedContext returns interface (context.Context)",
"NamedBytes returns interface (io.Writer)",
Expand All @@ -245,16 +236,20 @@ func TestAll(t *testing.T) {
mask: []string{"*.go", "github.com/foo/bar/*", "internal/sample/*"},
meta: map[string]string{}, // skipping any configuration to run default one.
want: []string{
"Min returns generic interface (T)",
"MixedReturnParameters returns generic interface (T)",
"MixedReturnParameters returns generic interface (K)",
"Max returns generic interface (foobar)",
"Foo returns generic interface (GENERIC)",
"SumIntsOrFloats returns generic interface (V)",
"Min returns generic interface (T) of type param ~int | ~float64 | ~float32",
"MixedReturnParameters returns generic interface (T) of type param ~int | ~float64 | ~float32",
"MixedReturnParameters returns generic interface (K) of type param ~int | ~float64 | ~float32",
"Max returns generic interface (foobar) of type param ~int | ~float64 | ~float32",
"SumIntsOrFloats returns generic interface (V) of type param int64 | float64",
"FuncWithGenericAny_NamedReturn returns generic interface (T_ANY) of type param any",
"FuncWithGenericAny returns generic interface (T_ANY) of type param any",
"Get returns generic interface (V_COMPARABLE) of type param comparable",
"Get returns generic interface (V_ANY) of type param any",
"s returns interface (github.com/foo/bar.Buzzer)",
"New returns interface (github.com/foo/bar.Buzzer)",
"NewDeclared returns interface (internal/sample.Doer)",
"newIDoer returns interface (example.iDoer)",
"newIDoerAny returns interface (example.iDoerAny)",
"NewNamedStruct returns interface (example.FooerBarer)",
},
})
Expand All @@ -263,7 +258,7 @@ func TestAll(t *testing.T) {
name: "all/stdlib/allow",
mask: []string{"*.go", "github.com/foo/bar/*", "internal/sample/*"},
meta: map[string]string{
"allow": types.NameStdLib, // allow only interfaces from standard library
"allow": types.NameStdLib, // allow only interfaces from standard library (e.g. io.Writer, fmt.Stringer)
},
want: []string{
"NewanonymousInterface returns interface (anonymous interface)",
Expand All @@ -274,33 +269,43 @@ func TestAll(t *testing.T) {
"errorAliasReturn returns interface (error)",
"errorTypeReturn returns interface (error)",
"newErrorInterface returns interface (error)",
"Min returns generic interface (T)",
"MixedReturnParameters returns generic interface (T)",
"MixedReturnParameters returns generic interface (K)",
"Max returns generic interface (foobar)",
"Foo returns generic interface (GENERIC)",
"SumIntsOrFloats returns generic interface (V)",
"Min returns generic interface (T) of type param ~int | ~float64 | ~float32",
"MixedReturnParameters returns generic interface (T) of type param ~int | ~float64 | ~float32",
"MixedReturnParameters returns generic interface (K) of type param ~int | ~float64 | ~float32",
"Max returns generic interface (foobar) of type param ~int | ~float64 | ~float32",
"SumIntsOrFloats returns generic interface (V) of type param int64 | float64",
"FuncWithGenericAny_NamedReturn returns generic interface (T_ANY) of type param any",
"FuncWithGenericAny returns generic interface (T_ANY) of type param any",
"Get returns generic interface (V_COMPARABLE) of type param comparable",
"Get returns generic interface (V_ANY) of type param any",
"FunctionAny returns interface (any)",
"FunctionInterface returns interface (interface{})",
"s returns interface (github.com/foo/bar.Buzzer)",
"New returns interface (github.com/foo/bar.Buzzer)",
"NewDeclared returns interface (internal/sample.Doer)",
"newIDoer returns interface (example.iDoer)",
"newIDoerAny returns interface (example.iDoerAny)",
"NewNamedStruct returns interface (example.FooerBarer)",
},
})

// Rejecting Only Generic Returns
tests = append(tests, testCase{
name: "generic/reject",
mask: []string{"*.go", "github.com/foo/bar/*", "internal/sample/*"},
meta: map[string]string{
"reject": types.NameGeneric, // allow only generic interfaces
"reject": types.NameGeneric, // reject only generic interfaces
},
want: []string{
"Min returns generic interface (T)",
"MixedReturnParameters returns generic interface (T)",
"MixedReturnParameters returns generic interface (K)",
"Max returns generic interface (foobar)",
"Foo returns generic interface (GENERIC)",
"SumIntsOrFloats returns generic interface (V)",
"Min returns generic interface (T) of type param ~int | ~float64 | ~float32",
"MixedReturnParameters returns generic interface (T) of type param ~int | ~float64 | ~float32",
"MixedReturnParameters returns generic interface (K) of type param ~int | ~float64 | ~float32",
"Max returns generic interface (foobar) of type param ~int | ~float64 | ~float32",
"SumIntsOrFloats returns generic interface (V) of type param int64 | float64",
"FuncWithGenericAny_NamedReturn returns generic interface (T_ANY) of type param any",
"FuncWithGenericAny returns generic interface (T_ANY) of type param any",
"Get returns generic interface (V_COMPARABLE) of type param comparable",
"Get returns generic interface (V_ANY) of type param any",
},
})

Expand All @@ -319,10 +324,13 @@ func TestAll(t *testing.T) {
"errorAliasReturn returns interface (error)",
"errorTypeReturn returns interface (error)",
"newErrorInterface returns interface (error)",
"FunctionAny returns interface (any)",
"FunctionInterface returns interface (interface{})",
"s returns interface (github.com/foo/bar.Buzzer)",
"New returns interface (github.com/foo/bar.Buzzer)",
"NewDeclared returns interface (internal/sample.Doer)",
"newIDoer returns interface (example.iDoer)",
"newIDoerAny returns interface (example.iDoerAny)",
"NewNamedStruct returns interface (example.FooerBarer)",
"NamedContext returns interface (context.Context)",
"NamedBytes returns interface (io.Writer)",
Expand Down
11 changes: 8 additions & 3 deletions analyzer/internal/types/iface.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type IFace struct {

Pos token.Pos // Token Position
FuncName string //
OfType string
}

func NewIssue(name string, interfaceType IType) IFace {
Expand All @@ -30,11 +31,15 @@ func (i *IFace) Enrich(f *ast.FuncDecl) {
}

func (i IFace) String() string {
if i.Type == Generic {
return fmt.Sprintf("%s returns generic interface (%s)", i.FuncName, i.Name)
if i.Type != Generic {
return fmt.Sprintf("%s returns interface (%s)", i.FuncName, i.Name)
}

return fmt.Sprintf("%s returns interface (%s)", i.FuncName, i.Name)
if i.OfType != "" {
return fmt.Sprintf("%s returns generic interface (%s) of type param %s", i.FuncName, i.Name, i.OfType)
}

return fmt.Sprintf("%s returns generic interface (%s)", i.FuncName, i.Name)
}

func (i IFace) HashString() string {
Expand Down
1 change: 1 addition & 0 deletions analyzer/testdata/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func newErrorInterface() error {

var _ error = (*errorInterface)(nil)

// never suppose to be linted
func newErrorInterfaceConcrete() *errorInterface {
return &errorInterface{}
}
Loading

0 comments on commit cc55cb0

Please sign in to comment.