diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml index fcb9fa4aa8b9..402ac6511373 100644 --- a/.golangci.next.reference.yml +++ b/.golangci.next.reference.yml @@ -114,6 +114,7 @@ linters: - unparam - unused - usestdlibvars + - usetesting - varnamelen - wastedassign - whitespace @@ -230,6 +231,7 @@ linters: - unparam - unused - usestdlibvars + - usetesting - varnamelen - wastedassign - whitespace @@ -3533,6 +3535,38 @@ linters-settings: # Default: false constant-kind: true + usetesting: + # Enable/disable `os.CreateTemp("", ...)` detections. + # Default: true + os-create-temp: false + + # Enable/disable `os.MkdirTemp()` detections. + # Default: true + os-mkdir-temp: false + + # Enable/disable `os.Setenv()` detections. + # Default: false + os-setenv: true + + # Enable/disable `os.TempDir()` detections. + # Default: false + os-temp-dir: true + + # Enable/disable `os.Chdir()` detections. + # Disabled if Go < 1.24. + # Default: true + os-chdir: false + + # Enable/disable `context.Background()` detections. + # Disabled if Go < 1.24. + # Default: true + context-background: false + + # Enable/disable `context.TODO()` detections. + # Disabled if Go < 1.24. + # Default: true + context-todo: false + unconvert: # Remove conversions that force intermediate rounding. # Default: false diff --git a/go.mod b/go.mod index 028cb143b97a..60545739c267 100644 --- a/go.mod +++ b/go.mod @@ -68,6 +68,7 @@ require ( github.com/lasiar/canonicalheader v1.1.2 github.com/ldez/gomoddirectives v0.2.4 github.com/ldez/tagliatelle v0.6.0 + github.com/ldez/usetesting v0.2.0 github.com/leonklingele/grouper v1.1.2 github.com/macabu/inamedparam v0.1.3 github.com/maratori/testableexamples v1.0.0 diff --git a/go.sum b/go.sum index dd6bf7214ebb..e9bdaeef62ce 100644 --- a/go.sum +++ b/go.sum @@ -355,6 +355,8 @@ github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJ github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= github.com/ldez/tagliatelle v0.6.0 h1:1Muumft/shmQ0x96vA6a/OUgTjamRt8jUlZPLm1ruwA= github.com/ldez/tagliatelle v0.6.0/go.mod h1:WeZ7TgEqq7fw/0Zj8BuQhh4+4KX1/+g0O11eygvClRA= +github.com/ldez/usetesting v0.2.0 h1:RnQ7HRVInPwsuX3FPesE/HxngIk3UrqEuDu20LMd2tI= +github.com/ldez/usetesting v0.2.0/go.mod h1:9Tk8OH9NL3Xi7lpvkGRpuY/cOmWmatxwVjRc+TkgLDg= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= diff --git a/jsonschema/golangci.next.jsonschema.json b/jsonschema/golangci.next.jsonschema.json index 3b0e72330dd3..91366feef0c5 100644 --- a/jsonschema/golangci.next.jsonschema.json +++ b/jsonschema/golangci.next.jsonschema.json @@ -428,6 +428,7 @@ "unparam", "unused", "usestdlibvars", + "usetesting", "varnamelen", "wastedassign", "whitespace", @@ -3325,6 +3326,40 @@ } } }, + "usetesting": { + "type": "object", + "additionalProperties": false, + "properties": { + "context-background": { + "type": "boolean", + "default": true + }, + "context-todo": { + "type": "boolean", + "default": true + }, + "os-chdir": { + "type": "boolean", + "default": true + }, + "os-mkdir-temp": { + "type": "boolean", + "default": true + }, + "os-setenv": { + "type": "boolean", + "default": false + }, + "os-create-temp": { + "type": "boolean", + "default": true + }, + "os-temp-dir": { + "type": "boolean", + "default": false + } + } + }, "unconvert": { "type": "object", "additionalProperties": false, diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 98e6bccb313f..6fca1003539f 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -179,6 +179,15 @@ var defaultLintersSettings = LintersSettings{ HTTPMethod: true, HTTPStatusCode: true, }, + UseTesting: UseTestingSettings{ + ContextBackground: true, + ContextTodo: true, + OSChdir: true, + OSMkdirTemp: true, + OSSetenv: false, + OSTempDir: false, + OSCreateTemp: true, + }, Varnamelen: VarnamelenSettings{ MaxDistance: 5, MinNameLength: 3, @@ -278,6 +287,7 @@ type LintersSettings struct { Unparam UnparamSettings Unused UnusedSettings UseStdlibVars UseStdlibVarsSettings + UseTesting UseTestingSettings Varnamelen VarnamelenSettings Whitespace WhitespaceSettings Wrapcheck WrapcheckSettings @@ -959,6 +969,16 @@ type UseStdlibVarsSettings struct { SyslogPriority bool `mapstructure:"syslog-priority"` // Deprecated } +type UseTestingSettings struct { + ContextBackground bool `mapstructure:"context-background"` + ContextTodo bool `mapstructure:"context-todo"` + OSChdir bool `mapstructure:"os-chdir"` + OSMkdirTemp bool `mapstructure:"os-mkdir-temp"` + OSSetenv bool `mapstructure:"os-setenv"` + OSTempDir bool `mapstructure:"os-temp-dir"` + OSCreateTemp bool `mapstructure:"os-create-temp"` +} + type UnconvertSettings struct { FastMath bool `mapstructure:"fast-math"` Safe bool `mapstructure:"safe"` diff --git a/pkg/golinters/usetesting/testdata/usetesting.go b/pkg/golinters/usetesting/testdata/usetesting.go new file mode 100644 index 000000000000..579908d48be5 --- /dev/null +++ b/pkg/golinters/usetesting/testdata/usetesting.go @@ -0,0 +1,26 @@ +//golangcitest:args -Eusetesting +package testdata + +import ( + "os" + "testing" +) + +func Test_osMkdirTemp(t *testing.T) { + os.MkdirTemp("", "") // want `os\.MkdirTemp\(\) could be replaced by t\.TempDir\(\) in .+` +} + +func Test_osSetenv(t *testing.T) { + os.Setenv("", "") +} + +func Test_osTempDir(t *testing.T) { + os.TempDir() +} + +func Test_osCreateTemp(t *testing.T) { + os.CreateTemp("", "") // want `os\.CreateTemp\("", \.\.\.\) could be replaced by os\.CreateTemp\(t\.TempDir\(\), \.\.\.\) in .+` + os.CreateTemp("", "xx") // want `os\.CreateTemp\("", \.\.\.\) could be replaced by os\.CreateTemp\(t\.TempDir\(\), \.\.\.\) in .+` + os.CreateTemp(os.TempDir(), "xx") + os.CreateTemp(t.TempDir(), "xx") +} diff --git a/pkg/golinters/usetesting/testdata/usetesting_configuration.go b/pkg/golinters/usetesting/testdata/usetesting_configuration.go new file mode 100644 index 000000000000..332e4ea9b12c --- /dev/null +++ b/pkg/golinters/usetesting/testdata/usetesting_configuration.go @@ -0,0 +1,27 @@ +//golangcitest:args -Eusetesting +//golangcitest:config_path testdata/usetesting_configuration.yml +package testdata + +import ( + "os" + "testing" +) + +func Test_osMkdirTemp(t *testing.T) { + os.MkdirTemp("", "") +} + +func Test_osTempDir(t *testing.T) { + os.TempDir() // want `os\.TempDir\(\) could be replaced by t\.TempDir\(\) in .+` +} + +func Test_osSetenv(t *testing.T) { + os.Setenv("", "") // want `os\.Setenv\(\) could be replaced by t\.Setenv\(\) in .+` +} + +func Test_osCreateTemp(t *testing.T) { + os.CreateTemp("", "") + os.CreateTemp("", "xx") + os.CreateTemp(os.TempDir(), "xx") // want `os\.TempDir\(\) could be replaced by t\.TempDir\(\) in .+` + os.CreateTemp(t.TempDir(), "xx") +} diff --git a/pkg/golinters/usetesting/testdata/usetesting_configuration.yml b/pkg/golinters/usetesting/testdata/usetesting_configuration.yml new file mode 100644 index 000000000000..fc877dd17f20 --- /dev/null +++ b/pkg/golinters/usetesting/testdata/usetesting_configuration.yml @@ -0,0 +1,9 @@ +linters-settings: + usetesting: + os-create-temp: false + os-mkdir-temp: false + os-setenv: true + os-temp-dir: true + os-chdir: false + context-background: false + context-todo: false diff --git a/pkg/golinters/usetesting/testdata/usetesting_go124.go b/pkg/golinters/usetesting/testdata/usetesting_go124.go new file mode 100644 index 000000000000..0ac122da1caa --- /dev/null +++ b/pkg/golinters/usetesting/testdata/usetesting_go124.go @@ -0,0 +1,41 @@ +//go:build go1.24 + +//golangcitest:args -Eusetesting +package testdata + +import ( + "context" + "os" + "testing" +) + +func Test_contextBackground(t *testing.T) { + context.Background() // want `context\.Background\(\) could be replaced by t\.Context\(\) in .+` +} + +func Test_contextTODO(t *testing.T) { + context.TODO() // want `context\.TODO\(\) could be replaced by t\.Context\(\) in .+` +} + +func Test_osChdir(t *testing.T) { + os.Chdir("") // want `os\.Chdir\(\) could be replaced by t\.Chdir\(\) in .+` +} + +func Test_osMkdirTemp(t *testing.T) { + os.MkdirTemp("", "") // want `os\.MkdirTemp\(\) could be replaced by t\.TempDir\(\) in .+` +} + +func Test_osSetenv(t *testing.T) { + os.Setenv("", "") +} + +func Test_osTempDir(t *testing.T) { + os.TempDir() +} + +func Test_osCreateTemp(t *testing.T) { + os.CreateTemp("", "") // want `os\.CreateTemp\("", \.\.\.\) could be replaced by os\.CreateTemp\(t\.TempDir\(\), \.\.\.\) in .+` + os.CreateTemp("", "xx") // want `os\.CreateTemp\("", \.\.\.\) could be replaced by os\.CreateTemp\(t\.TempDir\(\), \.\.\.\) in .+` + os.CreateTemp(os.TempDir(), "xx") + os.CreateTemp(t.TempDir(), "xx") +} diff --git a/pkg/golinters/usetesting/testdata/usetesting_go124_configuration.go b/pkg/golinters/usetesting/testdata/usetesting_go124_configuration.go new file mode 100644 index 000000000000..44774f3cb399 --- /dev/null +++ b/pkg/golinters/usetesting/testdata/usetesting_go124_configuration.go @@ -0,0 +1,42 @@ +//go:build go1.24 + +//golangcitest:args -Eusetesting +//golangcitest:config_path testdata/usetesting_go124_configuration.yml +package testdata + +import ( + "context" + "os" + "testing" +) + +func Test_contextBackground(t *testing.T) { + context.Background() +} + +func Test_contextTODO(t *testing.T) { + context.TODO() +} + +func Test_osChdir(t *testing.T) { + os.Chdir("") +} + +func Test_osMkdirTemp(t *testing.T) { + os.MkdirTemp("", "") +} + +func Test_osSetenv(t *testing.T) { + os.Setenv("", "") // want `os\.Setenv\(\) could be replaced by t\.Setenv\(\) in .+` +} + +func Test_osTempDir(t *testing.T) { + os.TempDir() // want `os\.TempDir\(\) could be replaced by t\.TempDir\(\) in .+` +} + +func Test_osCreateTemp(t *testing.T) { + os.CreateTemp("", "") + os.CreateTemp("", "xx") + os.CreateTemp(os.TempDir(), "xx") // want `os\.TempDir\(\) could be replaced by t\.TempDir\(\) in .+` + os.CreateTemp(t.TempDir(), "xx") +} diff --git a/pkg/golinters/usetesting/testdata/usetesting_go124_configuration.yml b/pkg/golinters/usetesting/testdata/usetesting_go124_configuration.yml new file mode 100644 index 000000000000..fc877dd17f20 --- /dev/null +++ b/pkg/golinters/usetesting/testdata/usetesting_go124_configuration.yml @@ -0,0 +1,9 @@ +linters-settings: + usetesting: + os-create-temp: false + os-mkdir-temp: false + os-setenv: true + os-temp-dir: true + os-chdir: false + context-background: false + context-todo: false diff --git a/pkg/golinters/usetesting/usetesting.go b/pkg/golinters/usetesting/usetesting.go new file mode 100644 index 000000000000..a21742fbd6d4 --- /dev/null +++ b/pkg/golinters/usetesting/usetesting.go @@ -0,0 +1,33 @@ +package usetesting + +import ( + "github.com/ldez/usetesting" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/goanalysis" +) + +func New(settings *config.UseTestingSettings) *goanalysis.Linter { + a := usetesting.NewAnalyzer() + + cfg := make(map[string]map[string]any) + if settings != nil { + cfg[a.Name] = map[string]any{ + "contextbackground": settings.ContextBackground, + "contexttodo": settings.ContextTodo, + "oschdir": settings.OSChdir, + "osmkdirtemp": settings.OSMkdirTemp, + "ossetenv": settings.OSSetenv, + "ostempdir": settings.OSTempDir, + "oscreatetemp": settings.OSCreateTemp, + } + } + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + cfg, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/golinters/usetesting/usetesting_integration_test.go b/pkg/golinters/usetesting/usetesting_integration_test.go new file mode 100644 index 000000000000..3ea6e1ffdaa5 --- /dev/null +++ b/pkg/golinters/usetesting/usetesting_integration_test.go @@ -0,0 +1,11 @@ +package usetesting + +import ( + "testing" + + "github.com/golangci/golangci-lint/test/testshared/integration" +) + +func TestFromTestdata(t *testing.T) { + integration.RunTestdata(t) +} diff --git a/pkg/lint/linter/config.go b/pkg/lint/linter/config.go index 6d6d4b17e7d4..1e438a0f0368 100644 --- a/pkg/lint/linter/config.go +++ b/pkg/lint/linter/config.go @@ -164,12 +164,20 @@ func (lc *Config) WithNoopFallback(cfg *config.Config, cond func(cfg *config.Con } func IsGoLowerThanGo122() func(cfg *config.Config) error { + return isGoLowerThanGo("1.22") +} + +func IsGoLowerThanGo124() func(cfg *config.Config) error { + return isGoLowerThanGo("1.24") +} + +func isGoLowerThanGo(v string) func(cfg *config.Config) error { return func(cfg *config.Config) error { - if cfg == nil || config.IsGoGreaterThanOrEqual(cfg.Run.Go, "1.22") { + if cfg == nil || config.IsGoGreaterThanOrEqual(cfg.Run.Go, v) { return nil } - return fmt.Errorf("this linter is disabled because the Go version (%s) of your project is lower than Go 1.22", cfg.Run.Go) + return fmt.Errorf("this linter is disabled because the Go version (%s) of your project is lower than Go %s", cfg.Run.Go, v) } } diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index f09fd951ce0e..2a6f46c135b0 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -105,6 +105,7 @@ import ( "github.com/golangci/golangci-lint/pkg/golinters/unparam" "github.com/golangci/golangci-lint/pkg/golinters/unused" "github.com/golangci/golangci-lint/pkg/golinters/usestdlibvars" + "github.com/golangci/golangci-lint/pkg/golinters/usetesting" "github.com/golangci/golangci-lint/pkg/golinters/varnamelen" "github.com/golangci/golangci-lint/pkg/golinters/wastedassign" "github.com/golangci/golangci-lint/pkg/golinters/whitespace" @@ -805,6 +806,12 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithPresets(linter.PresetStyle). WithURL("https://github.com/sashamelentyev/usestdlibvars"), + linter.NewConfig(usetesting.New(&cfg.LintersSettings.UseTesting)). + WithSince("v1.63.0"). + WithPresets(linter.PresetTest). + WithLoadForGoAnalysis(). + WithURL("https://github.com/ldez/usetesting"), + linter.NewConfig(linter.NewNoopDeprecated("varcheck", cfg, linter.DeprecationError)). WithSince("v1.0.0"). WithLoadForGoAnalysis().