From 553d7df5850617e216edbfa8e4bd65a3956170e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?W=C3=81NG=20Xu=C4=9Bru=C3=AC?= <1175567+xen0n@users.noreply.github.com> Date: Sun, 26 Mar 2023 06:35:39 +0800 Subject: [PATCH] feat: add the gosmopolitan linter (#3458) --- .golangci.reference.yml | 33 ++++++++++++++++ go.mod | 1 + go.sum | 2 + pkg/config/linters_settings.go | 14 +++++++ pkg/golinters/gosmopolitan.go | 32 ++++++++++++++++ pkg/lint/lintersdb/manager.go | 8 ++++ .../configs/gosmopolitan_allow_time_local.yml | 3 ++ .../gosmopolitan_dont_ignore_tests.yml | 3 ++ .../configs/gosmopolitan_escape_hatches.yml | 8 ++++ .../testdata/configs/gosmopolitan_scripts.yml | 6 +++ test/testdata/gosmopolitan.go | 25 ++++++++++++ .../testdata/gosmopolitan_allow_time_local.go | 12 ++++++ .../testdata/gosmopolitan_dont_ignore_test.go | 12 ++++++ test/testdata/gosmopolitan_escape_hatches.go | 38 +++++++++++++++++++ test/testdata/gosmopolitan_ignore_test.go | 12 ++++++ test/testdata/gosmopolitan_scripts.go | 14 +++++++ 16 files changed, 223 insertions(+) create mode 100644 pkg/golinters/gosmopolitan.go create mode 100644 test/testdata/configs/gosmopolitan_allow_time_local.yml create mode 100644 test/testdata/configs/gosmopolitan_dont_ignore_tests.yml create mode 100644 test/testdata/configs/gosmopolitan_escape_hatches.yml create mode 100644 test/testdata/configs/gosmopolitan_scripts.yml create mode 100644 test/testdata/gosmopolitan.go create mode 100644 test/testdata/gosmopolitan_allow_time_local.go create mode 100644 test/testdata/gosmopolitan_dont_ignore_test.go create mode 100644 test/testdata/gosmopolitan_escape_hatches.go create mode 100644 test/testdata/gosmopolitan_ignore_test.go create mode 100644 test/testdata/gosmopolitan_scripts.go diff --git a/.golangci.reference.yml b/.golangci.reference.yml index dacc57a1b44e..57e73ac791a0 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -948,6 +948,37 @@ linters-settings: # Default: "0600" G306: "0600" + gosmopolitan: + # Allow and ignore `time.Local` usages. + # + # Default: false + allow-time-local: true + # List of fully qualified names in the `full/pkg/path.name` form, to act as "i18n escape hatches". + # String literals inside call-like expressions to, or struct literals of those names, + # are exempt from the writing system check. + # + # Default: [] + escape-hatches: + - 'github.com/nicksnyder/go-i18n/v2/i18n.Message' + - 'example.com/your/project/i18n/markers.Raw' + - 'example.com/your/project/i18n/markers.OK' + - 'example.com/your/project/i18n/markers.TODO' + - 'command-line-arguments.Simple' + # Ignore test files. + # + # Default: true + ignore-tests: false + # List of Unicode scripts to watch for any usage in string literals. + # https://pkg.go.dev/unicode#pkg-variables + # + # Default: ["Han"] + watch-for-scripts: + - Devanagari + - Han + - Hangul + - Hiragana + - Katakana + govet: # Report about shadowed variables. # Default: false @@ -2049,6 +2080,7 @@ linters: - goprintffuncname - gosec - gosimple + - gosmopolitan - govet - grouper - ifshort @@ -2159,6 +2191,7 @@ linters: - goprintffuncname - gosec - gosimple + - gosmopolitan - govet - grouper - ifshort diff --git a/go.mod b/go.mod index 32abb9014fe0..9c61214502f0 100644 --- a/go.mod +++ b/go.mod @@ -106,6 +106,7 @@ require ( github.com/ultraware/whitespace v0.0.5 github.com/uudashr/gocognit v1.0.6 github.com/valyala/quicktemplate v1.7.0 + github.com/xen0n/gosmopolitan v1.2.1 github.com/yagipy/maintidx v1.0.0 github.com/yeya24/promlinter v0.2.0 gitlab.com/bosi/decorder v0.2.3 diff --git a/go.sum b/go.sum index 077a895c10db..f4ca45cb05d1 100644 --- a/go.sum +++ b/go.sum @@ -548,6 +548,8 @@ github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM= github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/xen0n/gosmopolitan v1.2.1 h1:3pttnTuFumELBRSh+KQs1zcz4fN6Zy7aB0xlnQSn1Iw= +github.com/xen0n/gosmopolitan v1.2.1/go.mod h1:JsHq/Brs1o050OOdmzHeOr0N7OtlnKRAGAsElF8xBQA= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index a98b2b64c408..e528e7f82053 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -61,6 +61,12 @@ var defaultLintersSettings = LintersSettings{ Gosec: GoSecSettings{ Concurrency: runtime.NumCPU(), }, + Gosmopolitan: GosmopolitanSettings{ + AllowTimeLocal: false, + EscapeHatches: []string{}, + IgnoreTests: true, + WatchForScripts: []string{"Han"}, + }, Ifshort: IfshortSettings{ MaxDeclLines: 1, MaxDeclChars: 30, @@ -167,6 +173,7 @@ type LintersSettings struct { Gomodguard GoModGuardSettings Gosec GoSecSettings Gosimple StaticCheckSettings + Gosmopolitan GosmopolitanSettings Govet GovetSettings Grouper GrouperSettings Ifshort IfshortSettings @@ -449,6 +456,13 @@ type GoSecSettings struct { Concurrency int `mapstructure:"concurrency"` } +type GosmopolitanSettings struct { + AllowTimeLocal bool `mapstructure:"allow-time-local"` + EscapeHatches []string `mapstructure:"escape-hatches"` + IgnoreTests bool `mapstructure:"ignore-tests"` + WatchForScripts []string `mapstructure:"watch-for-scripts"` +} + type GovetSettings struct { Go string `mapstructure:"-"` CheckShadowing bool `mapstructure:"check-shadowing"` diff --git a/pkg/golinters/gosmopolitan.go b/pkg/golinters/gosmopolitan.go new file mode 100644 index 000000000000..f18ed5e2c5cf --- /dev/null +++ b/pkg/golinters/gosmopolitan.go @@ -0,0 +1,32 @@ +package golinters + +import ( + "strings" + + "github.com/xen0n/gosmopolitan" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewGosmopolitan(s *config.GosmopolitanSettings) *goanalysis.Linter { + a := gosmopolitan.NewAnalyzer() + + cfgMap := map[string]map[string]interface{}{} + if s != nil { + cfgMap[a.Name] = map[string]interface{}{ + "allowtimelocal": s.AllowTimeLocal, + "escapehatches": strings.Join(s.EscapeHatches, ","), + "lookattests": !s.IgnoreTests, + "watchforscripts": strings.Join(s.WatchForScripts, ","), + } + } + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + cfgMap, + ).WithLoadMode(goanalysis.LoadModeTypesInfo) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 61a79f6f4173..c1e018fa957d 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -135,6 +135,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { gomodguardCfg *config.GoModGuardSettings gosecCfg *config.GoSecSettings gosimpleCfg *config.StaticCheckSettings + gosmopolitanCfg *config.GosmopolitanSettings govetCfg *config.GovetSettings grouperCfg *config.GrouperSettings ifshortCfg *config.IfshortSettings @@ -213,6 +214,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { gomodguardCfg = &m.cfg.LintersSettings.Gomodguard gosecCfg = &m.cfg.LintersSettings.Gosec gosimpleCfg = &m.cfg.LintersSettings.Gosimple + gosmopolitanCfg = &m.cfg.LintersSettings.Gosmopolitan govetCfg = &m.cfg.LintersSettings.Govet grouperCfg = &m.cfg.LintersSettings.Grouper ifshortCfg = &m.cfg.LintersSettings.Ifshort @@ -558,6 +560,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithAlternativeNames(megacheckName). WithURL("https://github.com/dominikh/go-tools/tree/master/simple"), + linter.NewConfig(golinters.NewGosmopolitan(gosmopolitanCfg)). + WithSince("v1.53.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/xen0n/gosmopolitan"), + linter.NewConfig(golinters.NewGovet(govetCfg)). WithSince("v1.0.0"). WithLoadForGoAnalysis(). diff --git a/test/testdata/configs/gosmopolitan_allow_time_local.yml b/test/testdata/configs/gosmopolitan_allow_time_local.yml new file mode 100644 index 000000000000..22c14053bbcf --- /dev/null +++ b/test/testdata/configs/gosmopolitan_allow_time_local.yml @@ -0,0 +1,3 @@ +linters-settings: + gosmopolitan: + allow-time-local: true diff --git a/test/testdata/configs/gosmopolitan_dont_ignore_tests.yml b/test/testdata/configs/gosmopolitan_dont_ignore_tests.yml new file mode 100644 index 000000000000..099296be3be0 --- /dev/null +++ b/test/testdata/configs/gosmopolitan_dont_ignore_tests.yml @@ -0,0 +1,3 @@ +linters-settings: + gosmopolitan: + ignore-tests: false diff --git a/test/testdata/configs/gosmopolitan_escape_hatches.yml b/test/testdata/configs/gosmopolitan_escape_hatches.yml new file mode 100644 index 000000000000..4783f84eb690 --- /dev/null +++ b/test/testdata/configs/gosmopolitan_escape_hatches.yml @@ -0,0 +1,8 @@ +linters-settings: + gosmopolitan: + escape-hatches: + - 'command-line-arguments.A' + - 'command-line-arguments.B' + - 'command-line-arguments.C' + - 'command-line-arguments.D' + - 'fmt.Println' diff --git a/test/testdata/configs/gosmopolitan_scripts.yml b/test/testdata/configs/gosmopolitan_scripts.yml new file mode 100644 index 000000000000..4d61bc18f355 --- /dev/null +++ b/test/testdata/configs/gosmopolitan_scripts.yml @@ -0,0 +1,6 @@ +linters-settings: + gosmopolitan: + watch-for-scripts: + - Hiragana + - Katakana + - Latin diff --git a/test/testdata/gosmopolitan.go b/test/testdata/gosmopolitan.go new file mode 100644 index 000000000000..bdb1aa0a3fe5 --- /dev/null +++ b/test/testdata/gosmopolitan.go @@ -0,0 +1,25 @@ +//golangcitest:args -Egosmopolitan +package testdata + +import ( + "fmt" + "time" +) + +type col struct { + // struct tag should not get reported + Foo string `gorm:"column:bar;not null;comment:'不应该报告这一行'"` +} + +func main() { + fmt.Println("hello world") + fmt.Println("你好,世界") // want `string literal contains rune in Han script` + fmt.Println("こんにちは、セカイ") + + _ = col{Foo: "hello"} + _ = col{Foo: "你好"} // want `string literal contains rune in Han script` + + x := time.Local // want `usage of time.Local` + _ = time.Now().In(x) + _ = time.Date(2023, 1, 2, 3, 4, 5, 678901234, time.Local) // want `usage of time.Local` +} diff --git a/test/testdata/gosmopolitan_allow_time_local.go b/test/testdata/gosmopolitan_allow_time_local.go new file mode 100644 index 000000000000..e2380740c91d --- /dev/null +++ b/test/testdata/gosmopolitan_allow_time_local.go @@ -0,0 +1,12 @@ +//golangcitest:args -Egosmopolitan +//golangcitest:config_path testdata/configs/gosmopolitan_allow_time_local.yml +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "time" +) + +func main() { + _ = time.Local +} diff --git a/test/testdata/gosmopolitan_dont_ignore_test.go b/test/testdata/gosmopolitan_dont_ignore_test.go new file mode 100644 index 000000000000..36bdc56bf4e1 --- /dev/null +++ b/test/testdata/gosmopolitan_dont_ignore_test.go @@ -0,0 +1,12 @@ +//golangcitest:args -Egosmopolitan +//golangcitest:config_path testdata/configs/gosmopolitan_dont_ignore_tests.yml +package testdata + +import ( + "time" +) + +func main() { + _ = "开启检查测试文件" // want `string literal contains rune in Han script` + _ = time.Local // want `usage of time.Local` +} diff --git a/test/testdata/gosmopolitan_escape_hatches.go b/test/testdata/gosmopolitan_escape_hatches.go new file mode 100644 index 000000000000..85b96be745f0 --- /dev/null +++ b/test/testdata/gosmopolitan_escape_hatches.go @@ -0,0 +1,38 @@ +//golangcitest:args -Egosmopolitan +//golangcitest:config_path testdata/configs/gosmopolitan_escape_hatches.yml +package testdata + +import ( + myAlias "fmt" +) + +type A string +type B = string +type C struct { + foo string + Bar string +} + +func D(fmt string) string { + myAlias.Println(fmt, "测试") + return myAlias.Sprintf("%s 测试", fmt) // want `string literal contains rune in Han script` +} + +type X struct { + baz string +} + +func main() { + _ = A("测试") + _ = string(A(string("测试"))) + _ = B("测试") + _ = C{ + foo: "测试", + Bar: "测试", + } + _ = D("测试") + + _ = &X{ + baz: "测试", // want `string literal contains rune in Han script` + } +} diff --git a/test/testdata/gosmopolitan_ignore_test.go b/test/testdata/gosmopolitan_ignore_test.go new file mode 100644 index 000000000000..6972c1359335 --- /dev/null +++ b/test/testdata/gosmopolitan_ignore_test.go @@ -0,0 +1,12 @@ +//golangcitest:args -Egosmopolitan +//golangcitest:expected_exitcode 0 +package testdata + +import ( + "time" +) + +func main() { + _ = "默认不检查测试文件" + _ = time.Local +} diff --git a/test/testdata/gosmopolitan_scripts.go b/test/testdata/gosmopolitan_scripts.go new file mode 100644 index 000000000000..04702991640f --- /dev/null +++ b/test/testdata/gosmopolitan_scripts.go @@ -0,0 +1,14 @@ +//golangcitest:args -Egosmopolitan +//golangcitest:config_path testdata/configs/gosmopolitan_scripts.yml +package testdata + +import ( + "fmt" +) + +func main() { + fmt.Println("hello world") // want `string literal contains rune in Latin script` + fmt.Println("should not report this line") //nolint:gosmopolitan + fmt.Println("你好,世界") + fmt.Println("こんにちは、セカイ") // want `string literal contains rune in Hiragana script` +}