diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml index dc121a7451af..2095a4680800 100644 --- a/.golangci.next.reference.yml +++ b/.golangci.next.reference.yml @@ -120,6 +120,7 @@ linters: - tparallel - unconvert - unparam + - unqueryvet - unused - usestdlibvars - usetesting @@ -233,6 +234,7 @@ linters: - tparallel - unconvert - unparam + - unqueryvet - unused - usestdlibvars - usetesting @@ -3884,6 +3886,21 @@ linters: # Default: false check-exported: true + unqueryvet: + # Enable SQL builder checking. + # Default: true + check-sql-builders: false + # Regex patterns for acceptable SELECT * usage. + # Default: + # - "SELECT \\* FROM information_schema\\..*" + # - "SELECT \\* FROM pg_catalog\\..*" + # - "SELECT COUNT\\(\\*\\)" + # - "SELECT MAX\\(\\*\\)" + # - "SELECT MIN\\(\\*\\)" + allowed-patterns: + - "SELECT \\* FROM temp_.*" + - "SELECT \\* FROM.*-- migration" + unused: # Mark all struct fields that have been written to as used. # Default: true diff --git a/go.mod b/go.mod index 5106fde1daaa..2e4f94beee10 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/Antonboom/testifylint v1.6.4 github.com/BurntSushi/toml v1.5.0 github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 + github.com/MirrexOne/unqueryvet v1.2.1 github.com/OpenPeeDeeP/depguard/v2 v2.2.1 github.com/alecthomas/chroma/v2 v2.20.0 github.com/alecthomas/go-check-sumtype v0.3.1 diff --git a/go.sum b/go.sum index 7cfe65f6452d..d99278a4eb83 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rW github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/MirrexOne/unqueryvet v1.2.1 h1:M+zdXMq84g+E1YOLa7g7ExN3dWfZQrdDSTCM7gC+m/A= +github.com/MirrexOne/unqueryvet v1.2.1/go.mod h1:IWwCwMQlSWjAIteW0t+28Q5vouyktfujzYznSIWiuOg= github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= diff --git a/jsonschema/golangci.next.jsonschema.json b/jsonschema/golangci.next.jsonschema.json index a96a2f207227..8940b10a714f 100644 --- a/jsonschema/golangci.next.jsonschema.json +++ b/jsonschema/golangci.next.jsonschema.json @@ -4000,6 +4000,24 @@ } } }, + "unqueryvetSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "check-sql-builders": { + "description": "Enable SQL builder checking.", + "type": "boolean", + "default": true + }, + "allowed-patterns": { + "description": "Regex patterns for acceptable SELECT * usage.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, "unusedSettings": { "type": "object", "additionalProperties": false, @@ -4796,6 +4814,9 @@ "unparam": { "$ref": "#/definitions/settings/definitions/unparamSettings" }, + "unqueryvet": { + "$ref": "#/definitions/settings/definitions/unqueryvetSettings" + }, "unused": { "$ref": "#/definitions/settings/definitions/unusedSettings" }, diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index a9e252e65a8a..dcad81ad3983 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -162,6 +162,9 @@ var defaultLintersSettings = LintersSettings{ SkipRegexp: `(export|internal)_test\.go`, AllowPackages: []string{"main"}, }, + Unqueryvet: UnqueryvetSettings{ + CheckSQLBuilders: true, + }, Unused: UnusedSettings{ FieldWritesAreUses: true, PostStatementsAreReads: false, @@ -250,6 +253,7 @@ type LintersSettings struct { Gomodguard GoModGuardSettings `mapstructure:"gomodguard"` Gosec GoSecSettings `mapstructure:"gosec"` Gosmopolitan GosmopolitanSettings `mapstructure:"gosmopolitan"` + Unqueryvet UnqueryvetSettings `mapstructure:"unqueryvet"` Govet GovetSettings `mapstructure:"govet"` Grouper GrouperSettings `mapstructure:"grouper"` Iface IfaceSettings `mapstructure:"iface"` @@ -1004,6 +1008,11 @@ type UnparamSettings struct { CheckExported bool `mapstructure:"check-exported"` } +type UnqueryvetSettings struct { + CheckSQLBuilders bool `mapstructure:"check-sql-builders"` + AllowedPatterns []string `mapstructure:"allowed-patterns"` +} + type UnusedSettings struct { FieldWritesAreUses bool `mapstructure:"field-writes-are-uses"` PostStatementsAreReads bool `mapstructure:"post-statements-are-reads"` diff --git a/pkg/golinters/unqueryvet/testdata/unqueryvet.go b/pkg/golinters/unqueryvet/testdata/unqueryvet.go new file mode 100644 index 000000000000..18f6a9ea4bcc --- /dev/null +++ b/pkg/golinters/unqueryvet/testdata/unqueryvet.go @@ -0,0 +1,43 @@ +//golangcitest:args -Eunqueryvet +package testdata + +import ( + "database/sql" + "fmt" + "strconv" +) + +func _() { + query := "SELECT * FROM users" // want "avoid SELECT \\* - explicitly specify needed columns for better performance, maintainability and stability" + + var db *sql.DB + rows, _ := db.Query("SELECT * FROM orders WHERE status = ?", "active") // want "avoid SELECT \\* - explicitly specify needed columns for better performance, maintainability and stability" + _ = rows + + count := "SELECT COUNT(*) FROM users" + _ = count + + goodQuery := "SELECT id, name, email FROM users" + _ = goodQuery + + fmt.Println(query) + + _ = strconv.Itoa(42) +} + +type SQLBuilder interface { + Select(columns ...string) SQLBuilder + From(table string) SQLBuilder + Where(condition string) SQLBuilder + Query() string +} + +func _(builder SQLBuilder) { + query := builder.Select("*").From("products") // want "avoid SELECT \\* in SQL builder - explicitly specify columns to prevent unnecessary data transfer and schema change issues" + _ = query +} + +func _(builder SQLBuilder) { + query := builder.Select("id", "name", "price").From("products") + _ = query +} diff --git a/pkg/golinters/unqueryvet/testdata/unqueryvet_custom.go b/pkg/golinters/unqueryvet/testdata/unqueryvet_custom.go new file mode 100644 index 000000000000..466604773d5d --- /dev/null +++ b/pkg/golinters/unqueryvet/testdata/unqueryvet_custom.go @@ -0,0 +1,29 @@ +//golangcitest:args -Eunqueryvet +//golangcitest:config_path testdata/unqueryvet_custom.yml +package testdata + +import ( + "database/sql" + "fmt" + "strconv" +) + +func _() { + query := "SELECT * FROM users" // want "avoid SELECT \\* - explicitly specify needed columns for better performance, maintainability and stability" + + var db *sql.DB + rows, _ := db.Query("SELECT * FROM orders WHERE status = ?", "active") // want "avoid SELECT \\* - explicitly specify needed columns for better performance, maintainability and stability" + _ = rows + + count := "SELECT COUNT(*) FROM users" + _ = count + + goodQuery := "SELECT id, name, email FROM users" + _ = goodQuery + + fmt.Println(query) + + _ = strconv.Itoa(42) +} + +// Custom allowed patterns test - SELECT * from temp tables should be allowed diff --git a/pkg/golinters/unqueryvet/testdata/unqueryvet_custom.yml b/pkg/golinters/unqueryvet/testdata/unqueryvet_custom.yml new file mode 100644 index 000000000000..e975619c678b --- /dev/null +++ b/pkg/golinters/unqueryvet/testdata/unqueryvet_custom.yml @@ -0,0 +1,6 @@ +version: "2" + +linters: + settings: + unqueryvet: + check-sql-builders: false diff --git a/pkg/golinters/unqueryvet/unqueryvet.go b/pkg/golinters/unqueryvet/unqueryvet.go new file mode 100644 index 000000000000..db4a4d75222a --- /dev/null +++ b/pkg/golinters/unqueryvet/unqueryvet.go @@ -0,0 +1,24 @@ +package unqueryvet + +import ( + "github.com/MirrexOne/unqueryvet" + pkgconfig "github.com/MirrexOne/unqueryvet/pkg/config" + + "github.com/golangci/golangci-lint/v2/pkg/config" + "github.com/golangci/golangci-lint/v2/pkg/goanalysis" +) + +func New(settings *config.UnqueryvetSettings) *goanalysis.Linter { + cfg := pkgconfig.DefaultSettings() + + if settings != nil { + cfg.CheckSQLBuilders = settings.CheckSQLBuilders + if len(settings.AllowedPatterns) > 0 { + cfg.AllowedPatterns = settings.AllowedPatterns + } + } + + return goanalysis. + NewLinterFromAnalyzer(unqueryvet.NewWithConfig(&cfg)). + WithLoadMode(goanalysis.LoadModeSyntax) +} diff --git a/pkg/golinters/unqueryvet/unqueryvet_integration_test.go b/pkg/golinters/unqueryvet/unqueryvet_integration_test.go new file mode 100644 index 000000000000..2b5590fe5607 --- /dev/null +++ b/pkg/golinters/unqueryvet/unqueryvet_integration_test.go @@ -0,0 +1,11 @@ +package unqueryvet + +import ( + "testing" + + "github.com/golangci/golangci-lint/v2/test/testshared/integration" +) + +func TestFromTestdata(t *testing.T) { + integration.RunTestdata(t) +} diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index da5eaf497ccf..4250679e84db 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -109,6 +109,7 @@ import ( "github.com/golangci/golangci-lint/v2/pkg/golinters/tparallel" "github.com/golangci/golangci-lint/v2/pkg/golinters/unconvert" "github.com/golangci/golangci-lint/v2/pkg/golinters/unparam" + "github.com/golangci/golangci-lint/v2/pkg/golinters/unqueryvet" "github.com/golangci/golangci-lint/v2/pkg/golinters/unused" "github.com/golangci/golangci-lint/v2/pkg/golinters/usestdlibvars" "github.com/golangci/golangci-lint/v2/pkg/golinters/usetesting" @@ -662,6 +663,10 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithLoadForGoAnalysis(). WithURL("https://github.com/mvdan/unparam"), + linter.NewConfig(unqueryvet.New(&cfg.Linters.Settings.Unqueryvet)). + WithSince("v2.5.0"). + WithURL("https://github.com/MirrexOne/unqueryvet"), + linter.NewConfig(unused.New(&cfg.Linters.Settings.Unused)). WithGroups(config.GroupStandard). WithSince("v1.20.0").