Skip to content

Commit baf610d

Browse files
uudashrldez
andauthored
Add iface linter (#4871)
Co-authored-by: Fernandez Ludovic <[email protected]>
1 parent 8a9cdad commit baf610d

17 files changed

+496
-0
lines changed

.golangci.next.reference.yml

+16
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ linters:
6363
- gosmopolitan
6464
- govet
6565
- grouper
66+
- iface
6667
- importas
6768
- inamedparam
6869
- ineffassign
@@ -178,6 +179,7 @@ linters:
178179
- gosmopolitan
179180
- govet
180181
- grouper
182+
- ifcae
181183
- importas
182184
- inamedparam
183185
- ineffassign
@@ -1928,6 +1930,20 @@ linters-settings:
19281930
# Default: false
19291931
var-require-grouping: true
19301932

1933+
iface:
1934+
# List of analyzers.
1935+
# Default: ["identical"]
1936+
enable:
1937+
- identical # Identifies interfaces in the same package that have identical method sets.
1938+
- unused # Identifies interfaces that are not used anywhere in the same package where the interface is defined.
1939+
- opaque # Identifies functions that return interfaces, but the actual returned value is always a single concrete implementation.
1940+
settings:
1941+
unused:
1942+
# List of packages path to exclude from the check.
1943+
# Default: []
1944+
exclude:
1945+
- github.com/example/log
1946+
19311947
importas:
19321948
# Do not allow unaliased imports of aliased packages.
19331949
# Default: false

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ require (
116116
github.com/ultraware/funlen v0.1.0
117117
github.com/ultraware/whitespace v0.1.1
118118
github.com/uudashr/gocognit v1.1.3
119+
github.com/uudashr/iface v1.2.0
119120
github.com/valyala/quicktemplate v1.8.0
120121
github.com/xen0n/gosmopolitan v1.2.2
121122
github.com/yagipy/maintidx v1.0.0

go.sum

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jsonschema/golangci.next.jsonschema.json

+39
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,13 @@
295295
"waitgroup-by-value"
296296
]
297297
},
298+
"iface-analyzers": {
299+
"enum": [
300+
"identical",
301+
"unused",
302+
"opaque"
303+
]
304+
},
298305
"linters": {
299306
"$comment": "anyOf with enum is used to allow auto completion of non-custom linters",
300307
"description": "Linters usable.",
@@ -356,6 +363,7 @@
356363
"gosmopolitan",
357364
"govet",
358365
"grouper",
366+
"iface",
359367
"ifshort",
360368
"importas",
361369
"inamedparam",
@@ -1922,6 +1930,37 @@
19221930
}
19231931
}
19241932
},
1933+
"iface": {
1934+
"type": "object",
1935+
"additionalProperties": false,
1936+
"properties": {
1937+
"enable": {
1938+
"description": "Enable analyzers by name.",
1939+
"type": "array",
1940+
"items": {
1941+
"$ref": "#/definitions/iface-analyzers"
1942+
}
1943+
},
1944+
"settings": {
1945+
"type": "object",
1946+
"additionalProperties": false,
1947+
"properties": {
1948+
"unused": {
1949+
"type": "object",
1950+
"additionalProperties": false,
1951+
"properties": {
1952+
"exclude": {
1953+
"type": "array",
1954+
"items": {
1955+
"type": "string"
1956+
}
1957+
}
1958+
}
1959+
}
1960+
}
1961+
}
1962+
}
1963+
},
19251964
"importas": {
19261965
"type": "object",
19271966
"additionalProperties": false,

pkg/config/linters_settings.go

+6
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ type LintersSettings struct {
237237
Gosmopolitan GosmopolitanSettings
238238
Govet GovetSettings
239239
Grouper GrouperSettings
240+
Iface IfaceSettings
240241
ImportAs ImportAsSettings
241242
Inamedparam INamedParamSettings
242243
InterfaceBloat InterfaceBloatSettings
@@ -656,6 +657,11 @@ type GrouperSettings struct {
656657
VarRequireGrouping bool `mapstructure:"var-require-grouping"`
657658
}
658659

660+
type IfaceSettings struct {
661+
Enable []string `mapstructure:"enable"`
662+
Settings map[string]map[string]any `mapstructure:"settings"`
663+
}
664+
659665
type ImportAsSettings struct {
660666
Alias []ImportAsAlias
661667
NoUnaliased bool `mapstructure:"no-unaliased"`

pkg/golinters/iface/iface.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package iface
2+
3+
import (
4+
"slices"
5+
6+
"github.com/uudashr/iface/identical"
7+
"github.com/uudashr/iface/opaque"
8+
"github.com/uudashr/iface/unused"
9+
"golang.org/x/tools/go/analysis"
10+
11+
"github.com/golangci/golangci-lint/pkg/config"
12+
"github.com/golangci/golangci-lint/pkg/goanalysis"
13+
)
14+
15+
func New(settings *config.IfaceSettings) *goanalysis.Linter {
16+
var conf map[string]map[string]any
17+
if settings != nil {
18+
conf = settings.Settings
19+
}
20+
21+
return goanalysis.NewLinter(
22+
"iface",
23+
"Detect the incorrect use of interfaces, helping developers avoid interface pollution.",
24+
analyzersFromSettings(settings),
25+
conf,
26+
).WithLoadMode(goanalysis.LoadModeTypesInfo)
27+
}
28+
29+
func analyzersFromSettings(settings *config.IfaceSettings) []*analysis.Analyzer {
30+
allAnalyzers := map[string]*analysis.Analyzer{
31+
"identical": identical.Analyzer,
32+
"unused": unused.Analyzer,
33+
"opaque": opaque.Analyzer,
34+
}
35+
36+
if settings == nil || len(settings.Enable) == 0 {
37+
// Default enable `identical` analyzer only
38+
return []*analysis.Analyzer{identical.Analyzer}
39+
}
40+
41+
var analyzers []*analysis.Analyzer
42+
for _, name := range uniqueNames(settings.Enable) {
43+
if _, ok := allAnalyzers[name]; !ok {
44+
// skip unknown analyzer
45+
continue
46+
}
47+
48+
analyzers = append(analyzers, allAnalyzers[name])
49+
}
50+
51+
return analyzers
52+
}
53+
54+
func uniqueNames(names []string) []string {
55+
slices.Sort(names)
56+
return slices.Compact(names)
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package iface
2+
3+
import (
4+
"testing"
5+
6+
"github.com/golangci/golangci-lint/test/testshared/integration"
7+
)
8+
9+
func TestFromTestdata(t *testing.T) {
10+
integration.RunTestdata(t)
11+
}
+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//golangcitest:args -Eiface
2+
//golangcitest:config_path testdata/iface_all.yml
3+
package testdata
4+
5+
import "fmt"
6+
7+
// identical
8+
9+
type Pinger interface { // want "identical: interface Pinger contains identical methods or type constraints from another interface, causing redundancy"
10+
Ping() error
11+
}
12+
13+
type Healthcheck interface { // want "identical: interface Healthcheck contains identical methods or type constraints from another interface, causing redundancy"
14+
Ping() error
15+
}
16+
17+
// opaque
18+
19+
type Server interface {
20+
Serve() error
21+
}
22+
23+
type server struct {
24+
addr string
25+
}
26+
27+
func (s server) Serve() error {
28+
return nil
29+
}
30+
31+
func NewServer(addr string) Server { // want "opaque: NewServer function return Server interface at the 1st result, abstract a single concrete implementation of \\*server"
32+
return &server{addr: addr}
33+
}
34+
35+
// unused
36+
37+
type User struct {
38+
ID string
39+
Name string
40+
}
41+
42+
type UserRepository interface { // want "unused: interface UserRepository is declared but not used within the package"
43+
UserOf(id string) (*User, error)
44+
}
45+
46+
type UserRepositorySQL struct {
47+
}
48+
49+
func (r *UserRepositorySQL) UserOf(id string) (*User, error) {
50+
return nil, nil
51+
}
52+
53+
type Granter interface {
54+
Grant(permission string) error
55+
}
56+
57+
func AllowAll(g Granter) error {
58+
return g.Grant("all")
59+
}
60+
61+
type Allower interface {
62+
Allow(permission string) error
63+
}
64+
65+
func Allow(x any) {
66+
_ = x.(Allower)
67+
fmt.Println("allow")
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
linters-settings:
2+
iface:
3+
enable:
4+
- unused
5+
- identical
6+
- opaque
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//golangcitest:args -Eiface
2+
package testdata
3+
4+
import "fmt"
5+
6+
// identical
7+
8+
type Pinger interface { // want "identical: interface Pinger contains identical methods or type constraints from another interface, causing redundancy"
9+
Ping() error
10+
}
11+
12+
type Healthcheck interface { // want "identical: interface Healthcheck contains identical methods or type constraints from another interface, causing redundancy"
13+
Ping() error
14+
}
15+
16+
// opaque
17+
18+
type Server interface {
19+
Serve() error
20+
}
21+
22+
type server struct {
23+
addr string
24+
}
25+
26+
func (s server) Serve() error {
27+
return nil
28+
}
29+
30+
func NewServer(addr string) Server {
31+
return &server{addr: addr}
32+
}
33+
34+
// unused
35+
36+
type User struct {
37+
ID string
38+
Name string
39+
}
40+
41+
type UserRepository interface {
42+
UserOf(id string) (*User, error)
43+
}
44+
45+
type UserRepositorySQL struct {
46+
}
47+
48+
func (r *UserRepositorySQL) UserOf(id string) (*User, error) {
49+
return nil, nil
50+
}
51+
52+
type Granter interface {
53+
Grant(permission string) error
54+
}
55+
56+
func AllowAll(g Granter) error {
57+
return g.Grant("all")
58+
}
59+
60+
type Allower interface {
61+
Allow(permission string) error
62+
}
63+
64+
func Allow(x any) {
65+
_ = x.(Allower)
66+
fmt.Println("allow")
67+
}

0 commit comments

Comments
 (0)