Skip to content

Commit

Permalink
Add iface linter (#4871)
Browse files Browse the repository at this point in the history
Co-authored-by: Fernandez Ludovic <[email protected]>
  • Loading branch information
uudashr and ldez authored Oct 22, 2024
1 parent 8a9cdad commit baf610d
Show file tree
Hide file tree
Showing 17 changed files with 496 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .golangci.next.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ linters:
- gosmopolitan
- govet
- grouper
- iface
- importas
- inamedparam
- ineffassign
Expand Down Expand Up @@ -178,6 +179,7 @@ linters:
- gosmopolitan
- govet
- grouper
- ifcae
- importas
- inamedparam
- ineffassign
Expand Down Expand Up @@ -1928,6 +1930,20 @@ linters-settings:
# Default: false
var-require-grouping: true

iface:
# List of analyzers.
# Default: ["identical"]
enable:
- identical # Identifies interfaces in the same package that have identical method sets.
- unused # Identifies interfaces that are not used anywhere in the same package where the interface is defined.
- opaque # Identifies functions that return interfaces, but the actual returned value is always a single concrete implementation.
settings:
unused:
# List of packages path to exclude from the check.
# Default: []
exclude:
- github.com/example/log

importas:
# Do not allow unaliased imports of aliased packages.
# Default: false
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ require (
github.com/ultraware/funlen v0.1.0
github.com/ultraware/whitespace v0.1.1
github.com/uudashr/gocognit v1.1.3
github.com/uudashr/iface v1.2.0
github.com/valyala/quicktemplate v1.8.0
github.com/xen0n/gosmopolitan v1.2.2
github.com/yagipy/maintidx v1.0.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions jsonschema/golangci.next.jsonschema.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,13 @@
"waitgroup-by-value"
]
},
"iface-analyzers": {
"enum": [
"identical",
"unused",
"opaque"
]
},
"linters": {
"$comment": "anyOf with enum is used to allow auto completion of non-custom linters",
"description": "Linters usable.",
Expand Down Expand Up @@ -356,6 +363,7 @@
"gosmopolitan",
"govet",
"grouper",
"iface",
"ifshort",
"importas",
"inamedparam",
Expand Down Expand Up @@ -1922,6 +1930,37 @@
}
}
},
"iface": {
"type": "object",
"additionalProperties": false,
"properties": {
"enable": {
"description": "Enable analyzers by name.",
"type": "array",
"items": {
"$ref": "#/definitions/iface-analyzers"
}
},
"settings": {
"type": "object",
"additionalProperties": false,
"properties": {
"unused": {
"type": "object",
"additionalProperties": false,
"properties": {
"exclude": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"importas": {
"type": "object",
"additionalProperties": false,
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/linters_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ type LintersSettings struct {
Gosmopolitan GosmopolitanSettings
Govet GovetSettings
Grouper GrouperSettings
Iface IfaceSettings
ImportAs ImportAsSettings
Inamedparam INamedParamSettings
InterfaceBloat InterfaceBloatSettings
Expand Down Expand Up @@ -656,6 +657,11 @@ type GrouperSettings struct {
VarRequireGrouping bool `mapstructure:"var-require-grouping"`
}

type IfaceSettings struct {
Enable []string `mapstructure:"enable"`
Settings map[string]map[string]any `mapstructure:"settings"`
}

type ImportAsSettings struct {
Alias []ImportAsAlias
NoUnaliased bool `mapstructure:"no-unaliased"`
Expand Down
57 changes: 57 additions & 0 deletions pkg/golinters/iface/iface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package iface

import (
"slices"

"github.com/uudashr/iface/identical"
"github.com/uudashr/iface/opaque"
"github.com/uudashr/iface/unused"
"golang.org/x/tools/go/analysis"

"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/goanalysis"
)

func New(settings *config.IfaceSettings) *goanalysis.Linter {
var conf map[string]map[string]any
if settings != nil {
conf = settings.Settings
}

return goanalysis.NewLinter(
"iface",
"Detect the incorrect use of interfaces, helping developers avoid interface pollution.",
analyzersFromSettings(settings),
conf,
).WithLoadMode(goanalysis.LoadModeTypesInfo)
}

func analyzersFromSettings(settings *config.IfaceSettings) []*analysis.Analyzer {
allAnalyzers := map[string]*analysis.Analyzer{
"identical": identical.Analyzer,
"unused": unused.Analyzer,
"opaque": opaque.Analyzer,
}

if settings == nil || len(settings.Enable) == 0 {
// Default enable `identical` analyzer only
return []*analysis.Analyzer{identical.Analyzer}
}

var analyzers []*analysis.Analyzer
for _, name := range uniqueNames(settings.Enable) {
if _, ok := allAnalyzers[name]; !ok {
// skip unknown analyzer
continue
}

analyzers = append(analyzers, allAnalyzers[name])
}

return analyzers
}

func uniqueNames(names []string) []string {
slices.Sort(names)
return slices.Compact(names)
}
11 changes: 11 additions & 0 deletions pkg/golinters/iface/iface_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package iface

import (
"testing"

"github.com/golangci/golangci-lint/test/testshared/integration"
)

func TestFromTestdata(t *testing.T) {
integration.RunTestdata(t)
}
68 changes: 68 additions & 0 deletions pkg/golinters/iface/testdata/iface_all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//golangcitest:args -Eiface
//golangcitest:config_path testdata/iface_all.yml
package testdata

import "fmt"

// identical

type Pinger interface { // want "identical: interface Pinger contains identical methods or type constraints from another interface, causing redundancy"
Ping() error
}

type Healthcheck interface { // want "identical: interface Healthcheck contains identical methods or type constraints from another interface, causing redundancy"
Ping() error
}

// opaque

type Server interface {
Serve() error
}

type server struct {
addr string
}

func (s server) Serve() error {
return nil
}

func NewServer(addr string) Server { // want "opaque: NewServer function return Server interface at the 1st result, abstract a single concrete implementation of \\*server"
return &server{addr: addr}
}

// unused

type User struct {
ID string
Name string
}

type UserRepository interface { // want "unused: interface UserRepository is declared but not used within the package"
UserOf(id string) (*User, error)
}

type UserRepositorySQL struct {
}

func (r *UserRepositorySQL) UserOf(id string) (*User, error) {
return nil, nil
}

type Granter interface {
Grant(permission string) error
}

func AllowAll(g Granter) error {
return g.Grant("all")
}

type Allower interface {
Allow(permission string) error
}

func Allow(x any) {
_ = x.(Allower)
fmt.Println("allow")
}
6 changes: 6 additions & 0 deletions pkg/golinters/iface/testdata/iface_all.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
linters-settings:
iface:
enable:
- unused
- identical
- opaque
67 changes: 67 additions & 0 deletions pkg/golinters/iface/testdata/iface_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//golangcitest:args -Eiface
package testdata

import "fmt"

// identical

type Pinger interface { // want "identical: interface Pinger contains identical methods or type constraints from another interface, causing redundancy"
Ping() error
}

type Healthcheck interface { // want "identical: interface Healthcheck contains identical methods or type constraints from another interface, causing redundancy"
Ping() error
}

// opaque

type Server interface {
Serve() error
}

type server struct {
addr string
}

func (s server) Serve() error {
return nil
}

func NewServer(addr string) Server {
return &server{addr: addr}
}

// unused

type User struct {
ID string
Name string
}

type UserRepository interface {
UserOf(id string) (*User, error)
}

type UserRepositorySQL struct {
}

func (r *UserRepositorySQL) UserOf(id string) (*User, error) {
return nil, nil
}

type Granter interface {
Grant(permission string) error
}

func AllowAll(g Granter) error {
return g.Grant("all")
}

type Allower interface {
Allow(permission string) error
}

func Allow(x any) {
_ = x.(Allower)
fmt.Println("allow")
}
Loading

0 comments on commit baf610d

Please sign in to comment.