diff --git a/.golangci.reference.yml b/.golangci.reference.yml index c21108ba81e8..f5731828f706 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -686,6 +686,15 @@ linters-settings: - OPTIMIZE # marks code that should be optimized before merging - HACK # marks hack-around that should be removed before merging + gofactory: + # List of glob packages, which can create structures without factories inside the glob package. + # Default: [] + packageGlobs: + - github.com/author/repository/path/to/package/** + # Use a factory to initiate a structure for glob packages only. + # Default: false + packageGlobsOnly: true + gofmt: # Simplify code: gofmt with `-s` option. # Default: true @@ -2410,6 +2419,7 @@ linters: - godot - godox - goerr113 + - gofactory - gofmt - gofumpt - goheader @@ -2531,6 +2541,7 @@ linters: - godot - godox - goerr113 + - gofactory - gofmt - gofumpt - goheader diff --git a/go.mod b/go.mod index 92a81fb903b4..0c1836a667d7 100644 --- a/go.mod +++ b/go.mod @@ -69,6 +69,7 @@ require ( github.com/leonklingele/grouper v1.1.1 github.com/lufeee/execinquery v1.2.1 github.com/macabu/inamedparam v0.1.3 + github.com/maranqz/gofactory v1.0.0-rc1 github.com/maratori/testableexamples v1.0.0 github.com/maratori/testpackage v1.1.1 github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 diff --git a/go.sum b/go.sum index 0b979a22d37e..eeb6cc4a4bda 100644 --- a/go.sum +++ b/go.sum @@ -360,6 +360,8 @@ github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/maranqz/gofactory v1.0.0-rc1 h1:nZGZtEDGrL7gD9PKHiB1Ng1rhZlgg5/Hb3OfIgpMu84= +github.com/maranqz/gofactory v1.0.0-rc1/go.mod h1:+MwH9KkNAd2Q9R88ECuoGnYCCGmgGebKiJz2Gwjcxu8= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index fd7c5f80e796..717a9168fc15 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -204,6 +204,7 @@ type LintersSettings struct { Gocyclo GoCycloSettings Godot GodotSettings Godox GodoxSettings + Gofactory GoFactorySettings Gofmt GoFmtSettings Gofumpt GofumptSettings Goheader GoHeaderSettings @@ -486,6 +487,11 @@ type GodoxSettings struct { Keywords []string } +type GoFactorySettings struct { + PackageGlobs []string `mapstructure:"packageGlobs"` + PackageGlobsOnly bool `mapstructure:"packageGlobsOnly"` +} + type GoFmtSettings struct { Simplify bool RewriteRules []GoFmtRewriteRule `mapstructure:"rewrite-rules"` diff --git a/pkg/golinters/gofactory.go b/pkg/golinters/gofactory.go new file mode 100644 index 000000000000..9075734a99f8 --- /dev/null +++ b/pkg/golinters/gofactory.go @@ -0,0 +1,30 @@ +package golinters + +import ( + "github.com/maranqz/gofactory" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewGoFactory(settings *config.GoFactorySettings) *goanalysis.Linter { + analyzer := gofactory.NewAnalyzer() + + cfg := make(map[string]map[string]any) + if settings != nil { + cfg[analyzer.Name] = map[string]any{} + + if len(settings.PackageGlobs) > 0 { + cfg[analyzer.Name]["packageGlobs"] = settings.PackageGlobs + cfg[analyzer.Name]["packageGlobsOnly"] = settings.PackageGlobsOnly + } + } + + return goanalysis.NewLinter( + analyzer.Name, + analyzer.Doc, + []*analysis.Analyzer{analyzer}, + cfg, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index e30d48e4f10c..7188ee3532ee 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -90,6 +90,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { gocycloCfg *config.GoCycloSettings godotCfg *config.GodotSettings godoxCfg *config.GodoxSettings + goFactoryCfg *config.GoFactorySettings gofmtCfg *config.GoFmtSettings gofumptCfg *config.GofumptSettings goheaderCfg *config.GoHeaderSettings @@ -176,6 +177,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { gocycloCfg = &m.cfg.LintersSettings.Gocyclo godotCfg = &m.cfg.LintersSettings.Godot godoxCfg = &m.cfg.LintersSettings.Godox + goFactoryCfg = &m.cfg.LintersSettings.Gofactory gofmtCfg = &m.cfg.LintersSettings.Gofmt gofumptCfg = &m.cfg.LintersSettings.Gofumpt goheaderCfg = &m.cfg.LintersSettings.Goheader @@ -492,6 +494,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithLoadForGoAnalysis(). WithURL("https://github.com/Djarvur/go-err113"), + linter.NewConfig(golinters.NewGoFactory(goFactoryCfg)). + WithSince("1.56.0"). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/maranqz/gofactory"), + linter.NewConfig(golinters.NewGofmt(gofmtCfg)). WithSince("v1.0.0"). WithPresets(linter.PresetFormatting). diff --git a/test/run_test.go b/test/run_test.go index 2ec4b64280a0..7cb9c60454f4 100644 --- a/test/run_test.go +++ b/test/run_test.go @@ -137,7 +137,7 @@ func TestCgoOk(t *testing.T) { "--timeout=3m", "--enable-all", "-D", - "nosnakecase,gci", + "nosnakecase,gci,gofactory", ). WithTargetPath(testdataDir, "cgo"). Runner(). @@ -355,7 +355,6 @@ func TestLineDirectiveProcessedFiles(t *testing.T) { func TestUnsafeOk(t *testing.T) { testshared.NewRunnerBuilder(t). WithNoConfig(). - WithArgs("--enable-all"). WithTargetPath(testdataDir, "unsafe"). Runner(). Install(). diff --git a/test/testdata/configs/go_factory_package_globs_only.yml b/test/testdata/configs/go_factory_package_globs_only.yml new file mode 100644 index 000000000000..a7c965484e6f --- /dev/null +++ b/test/testdata/configs/go_factory_package_globs_only.yml @@ -0,0 +1,5 @@ +linters-settings: + gofactory: + packageGlobs: + - net/url/** + packageGlobsOnly: true diff --git a/test/testdata/gofactory.go b/test/testdata/gofactory.go new file mode 100644 index 000000000000..7b79703edb50 --- /dev/null +++ b/test/testdata/gofactory.go @@ -0,0 +1,32 @@ +//golangcitest:args -Egofactory +package testdata + +import ( + "net/http" + alias_blocked "net/http" +) + +type Struct struct{} + +var ( + defaultGlobalRequest = http.Request{} // want `Use factory for http.Request` + defaultGlobalRequestPtr = &http.Request{} // want `Use factory for http.Request` +) + +func Default() { + _ = http.Request{} // want `Use factory for http.Request` + _ = &http.Request{} // want `Use factory for http.Request` + + _ = []http.Request{{}, http.Request{}} // want `Use factory for http.Request` + _ = []*http.Request{{}, &http.Request{}} // want `Use factory for http.Request` + + call(http.Request{}) // want `Use factory for http.Request` + + _ = []Struct{{}, {}} +} + +func call(_ http.Request) {} + +func alias() { + _ = alias_blocked.Request{} // want `Use factory for http.Request` +} diff --git a/test/testdata/gofactory_package_globs_only.go b/test/testdata/gofactory_package_globs_only.go new file mode 100644 index 000000000000..ebddddbee499 --- /dev/null +++ b/test/testdata/gofactory_package_globs_only.go @@ -0,0 +1,46 @@ +//golangcitest:args -Egofactory +//golangcitest:config_path configs/go_factory_package_globs_only.yml +package testdata + +import ( + "net/http" + neturl "net/url" +) + +var ( + nestedGlobalRequest = http.Request{} + nestedGlobalRequestPtr = &http.Request{} + + blockedGlobalURL = neturl.URL{} // want `Use factory for url.URL` + blockedGlobalURLPtr = &neturl.URL{} // want `Use factory for url.URL` +) + +func Blocked() { + _ = http.Request{} + _ = &http.Request{} + + _ = neturl.URL{} // want `Use factory for url.URL` + _ = &neturl.URL{} // want `Use factory for url.URL` +} + +type URL struct { + Scheme string + Opaque string + User *neturl.Userinfo + Host string + Path string + RawPath string + OmitHost bool + ForceQuery bool + RawQuery string + Fragment string + RawFragment string +} + +func Casting() { + _ = neturl.URL(URL{}) // want `Use factory for url.URL` + + uPtr, _ := neturl.Parse("") + u := *uPtr + _ = URL(u) +}