diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml index f20874515ede..7be05e74d9da 100644 --- a/.golangci.next.reference.yml +++ b/.golangci.next.reference.yml @@ -1223,7 +1223,8 @@ linters-settings: var-require-grouping: true iface: - # By default set to empty. Leave it empty means all analyzers are enabled. + # By default, set to empty. + # Empty means that all analyzers are enabled. # Default: [] enable: - unused @@ -1231,7 +1232,7 @@ linters-settings: - opaque settings: unused: - # Comma-separated list of packages to exclude from the check. + # List of packages path to exclude from the check. # Default: [] exclude: - github.com/example/log diff --git a/jsonschema/golangci.next.jsonschema.json b/jsonschema/golangci.next.jsonschema.json index 994039bb5265..2681b85662ff 100644 --- a/jsonschema/golangci.next.jsonschema.json +++ b/jsonschema/golangci.next.jsonschema.json @@ -1928,6 +1928,17 @@ "items": { "$ref": "#/definitions/iface-analyzers" } + }, + "settings": { + "type": "object", + "propertyNames": { + "$ref": "#/definitions/iface-analyzers" + }, + "patternProperties": { + "^.*$": { + "type": "object" + } + } } } }, diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 57e5555d54bc..cdc2eefd57f6 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -650,7 +650,8 @@ type GrouperSettings struct { } type IfaceSettings struct { - Enable []string `mapstructure:"enable"` + Enable []string `mapstructure:"enable"` + Settings map[string]map[string]any `mapstructure:"settings"` } type ImportAsSettings struct { diff --git a/pkg/golinters/iface/iface.go b/pkg/golinters/iface/iface.go index 375b13606503..1176dcf08a5f 100644 --- a/pkg/golinters/iface/iface.go +++ b/pkg/golinters/iface/iface.go @@ -6,69 +6,47 @@ import ( "github.com/uudashr/iface/identical" "github.com/uudashr/iface/opaque" "github.com/uudashr/iface/unused" + "golang.org/x/exp/maps" "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" ) -var allAnalyzers = []*analysis.Analyzer{ - unused.Analyzer, - identical.Analyzer, - opaque.Analyzer, -} - func New(settings *config.IfaceSettings) *goanalysis.Linter { var conf map[string]map[string]any - - analyzers := analyzersFromSettings(settings) + if settings != nil { + conf = settings.Settings + } return goanalysis.NewLinter( "iface", "Detect the incorrect use of interfaces, helping developers avoid interface pollution.", - analyzers, + analyzersFromSettings(settings), conf, ).WithLoadMode(goanalysis.LoadModeTypesInfo) } func analyzersFromSettings(settings *config.IfaceSettings) []*analysis.Analyzer { - if settings == nil || len(settings.Enable) == 0 { - return allAnalyzers + allAnalyzers := map[string]*analysis.Analyzer{ + "unused": unused.Analyzer, + "identical": identical.Analyzer, + "opaque": opaque.Analyzer, } - enabledNames := uniqueNames(settings.Enable) + if settings == nil || len(settings.Enable) == 0 { + return maps.Values(allAnalyzers) + } var analyzers []*analysis.Analyzer - - for _, a := range allAnalyzers { - found := slices.ContainsFunc(enabledNames, func(name string) bool { - return name == a.Name - }) - - if !found { - continue - } - - analyzers = append(analyzers, a) + for _, name := range uniqueNames(settings.Enable) { + analyzers = append(analyzers, allAnalyzers[name]) } return analyzers } func uniqueNames(names []string) []string { - if len(names) == 0 { - return nil - } - - namesMap := map[string]struct{}{} - for _, name := range names { - namesMap[name] = struct{}{} - } - - uniqueNames := make([]string, 0, len(namesMap)) - - for name := range namesMap { - uniqueNames = append(uniqueNames, name) - } - return uniqueNames + slices.Sort(names) + return slices.Compact(names) } diff --git a/pkg/golinters/iface/iface_test.go b/pkg/golinters/iface/iface_test.go deleted file mode 100644 index 95cb71c98c04..000000000000 --- a/pkg/golinters/iface/iface_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package iface - -import ( - "testing" - - "github.com/golangci/golangci-lint/pkg/config" -) - -func TestAnalyzersFromSettings(t *testing.T) { - testCases := map[string]struct { - enable []string - expectedEnabled []string - }{ - "nil analyzers": { - enable: nil, - expectedEnabled: []string{"unused", "identical", "opaque"}, - }, - "empty analyzers": { - enable: []string{}, - expectedEnabled: []string{"unused", "identical", "opaque"}, - }, - "unused only": { - enable: []string{"unused"}, - expectedEnabled: []string{"unused"}, - }, - "some analyzers": { - enable: []string{"unused", "opaque"}, - expectedEnabled: []string{"unused", "opaque"}, - }, - "duplicate analyzers": { - enable: []string{"unused", "opaque", "unused"}, - expectedEnabled: []string{"unused", "opaque"}, - }, - "all analyzers": { - enable: []string{"unused", "opaque", "identical"}, - expectedEnabled: []string{"unused", "identical", "opaque"}, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - settings := &config.IfaceSettings{Enable: tc.enable} - analyzers := analyzersFromSettings(settings) - - if len(analyzers) != len(tc.expectedEnabled) { - t.Errorf("expected %d analyzers, got %d", len(tc.enable), len(analyzers)) - } - - LoopSettings: - for _, a := range analyzers { - for _, name := range tc.expectedEnabled { - if a.Name == name { - continue LoopSettings - } - } - - t.Errorf("unexpected analyzer %q", a.Name) - } - }) - } -} diff --git a/pkg/golinters/iface/testdata/identical.go b/pkg/golinters/iface/testdata/identical.go deleted file mode 100644 index f13e756aaecf..000000000000 --- a/pkg/golinters/iface/testdata/identical.go +++ /dev/null @@ -1,11 +0,0 @@ -//golangcitest:args -Eiface -//golangcitest:config_path testdata/identical.yml -package testdata - -type Pinger interface { // want "interface Pinger contains identical methods or type constraints from another interface, causing redundancy" - Ping() error -} - -type Healthcheck interface { // want "interface Healthcheck contains identical methods or type constraints from another interface, causing redundancy" - Ping() error -} diff --git a/pkg/golinters/iface/testdata/iface.go b/pkg/golinters/iface/testdata/iface.go new file mode 100644 index 000000000000..f819334e40c6 --- /dev/null +++ b/pkg/golinters/iface/testdata/iface.go @@ -0,0 +1,64 @@ +//golangcitest:args -Eiface +package testdata + +// identical + +type Pinger interface { // want "unused: interface Pinger is declared but not used within the package" + Ping() error +} + +type Healthcheck interface { // want "unused: interface Healthcheck is declared but not used within the package" + 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 interface{}) { + _ = x.(Allower) +} diff --git a/pkg/golinters/iface/testdata/iface_identical.go b/pkg/golinters/iface/testdata/iface_identical.go new file mode 100644 index 000000000000..316b35ac2bf9 --- /dev/null +++ b/pkg/golinters/iface/testdata/iface_identical.go @@ -0,0 +1,65 @@ +//golangcitest:args -Eiface +//golangcitest:config_path testdata/iface_identical.yml +package testdata + +// 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 interface{}) { + _ = x.(Allower) +} diff --git a/pkg/golinters/iface/testdata/identical.yml b/pkg/golinters/iface/testdata/iface_identical.yml similarity index 100% rename from pkg/golinters/iface/testdata/identical.yml rename to pkg/golinters/iface/testdata/iface_identical.yml diff --git a/pkg/golinters/iface/testdata/iface_opaque.go b/pkg/golinters/iface/testdata/iface_opaque.go new file mode 100644 index 000000000000..3f3e632a12ec --- /dev/null +++ b/pkg/golinters/iface/testdata/iface_opaque.go @@ -0,0 +1,65 @@ +//golangcitest:args -Eiface +//golangcitest:config_path testdata/iface_opaque.yml +package testdata + +// identical + +type Pinger interface { + Ping() error +} + +type Healthcheck interface { + 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 { + 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 interface{}) { + _ = x.(Allower) +} diff --git a/pkg/golinters/iface/testdata/opaque.yml b/pkg/golinters/iface/testdata/iface_opaque.yml similarity index 100% rename from pkg/golinters/iface/testdata/opaque.yml rename to pkg/golinters/iface/testdata/iface_opaque.yml diff --git a/pkg/golinters/iface/testdata/iface_unused.go b/pkg/golinters/iface/testdata/iface_unused.go new file mode 100644 index 000000000000..db3f94af10a3 --- /dev/null +++ b/pkg/golinters/iface/testdata/iface_unused.go @@ -0,0 +1,65 @@ +//golangcitest:args -Eiface +//golangcitest:config_path testdata/iface_unused.yml +package testdata + +// identical + +type Pinger interface { // want "unused: interface Pinger is declared but not used within the package" + Ping() error +} + +type Healthcheck interface { // want "unused: interface Healthcheck is declared but not used within the package" + 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 { // 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 interface{}) { + _ = x.(Allower) +} diff --git a/pkg/golinters/iface/testdata/unused.yml b/pkg/golinters/iface/testdata/iface_unused.yml similarity index 100% rename from pkg/golinters/iface/testdata/unused.yml rename to pkg/golinters/iface/testdata/iface_unused.yml diff --git a/pkg/golinters/iface/testdata/unused.go b/pkg/golinters/iface/testdata/iface_unused_settings.go similarity index 51% rename from pkg/golinters/iface/testdata/unused.go rename to pkg/golinters/iface/testdata/iface_unused_settings.go index 69cc117cfd3a..7c8b4491dd8c 100644 --- a/pkg/golinters/iface/testdata/unused.go +++ b/pkg/golinters/iface/testdata/iface_unused_settings.go @@ -1,13 +1,44 @@ //golangcitest:args -Eiface -//golangcitest:config_path testdata/unused.yml +//golangcitest:config_path testdata/iface_unused_settings.yml +//golangcitest:expected_exitcode 0 package testdata +// identical + +type Pinger interface { + Ping() error +} + +type Healthcheck interface { + 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 { // want "interface UserRepository is declared but not used within the package" +type UserRepository interface { UserOf(id string) (*User, error) } diff --git a/pkg/golinters/iface/testdata/iface_unused_settings.yml b/pkg/golinters/iface/testdata/iface_unused_settings.yml new file mode 100644 index 000000000000..8e885bf188e1 --- /dev/null +++ b/pkg/golinters/iface/testdata/iface_unused_settings.yml @@ -0,0 +1,8 @@ +linters-settings: + iface: + enable: + - unused + settings: + unused: + exclude: + - "command-line-arguments" # fake package name used for test inside golangci-lint diff --git a/pkg/golinters/iface/testdata/opaque.go b/pkg/golinters/iface/testdata/opaque.go deleted file mode 100644 index de401dd8d526..000000000000 --- a/pkg/golinters/iface/testdata/opaque.go +++ /dev/null @@ -1,19 +0,0 @@ -//golangcitest:args -Eiface -//golangcitest:config_path testdata/opaque.yml -package testdata - -type Server interface { - Serve() error -} - -type server struct { - addr string -} - -func (s server) Serve() error { - return nil -} - -func NewServer(addr string) Server { // want "NewServer function return Server interface at the 1st result, abstract a single concrete implementation of \\*server" - return &server{addr: addr} -} diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index 5856ff6a5743..236295c87895 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -473,18 +473,18 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithPresets(linter.PresetStyle). WithURL("https://github.com/leonklingele/grouper"), - linter.NewConfig(iface.New(&cfg.LintersSettings.Iface)). - WithSince("v1.60.0"). - WithLoadForGoAnalysis(). - WithPresets(linter.PresetStyle, linter.PresetMetaLinter). - WithURL("http://github.com/uudashr/iface"), - linter.NewConfig(linter.NewNoopDeprecated("ifshort", cfg, linter.DeprecationError)). WithSince("v1.36.0"). WithPresets(linter.PresetStyle). WithURL("https://github.com/esimonov/ifshort"). DeprecatedError("The repository of the linter has been deprecated by the owner.", "v1.48.0", ""), + linter.NewConfig(iface.New(&cfg.LintersSettings.Iface)). + WithSince("v1.62.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetStyle, linter.PresetMetaLinter). + WithURL("https://github.com/uudashr/iface"), + linter.NewConfig(importas.New(&cfg.LintersSettings.ImportAs)). WithSince("v1.38.0"). WithPresets(linter.PresetStyle).