Skip to content

Commit 553d7df

Browse files
authored
feat: add the gosmopolitan linter (#3458)
1 parent b29a4f6 commit 553d7df

16 files changed

+223
-0
lines changed

.golangci.reference.yml

+33
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,37 @@ linters-settings:
948948
# Default: "0600"
949949
G306: "0600"
950950

951+
gosmopolitan:
952+
# Allow and ignore `time.Local` usages.
953+
#
954+
# Default: false
955+
allow-time-local: true
956+
# List of fully qualified names in the `full/pkg/path.name` form, to act as "i18n escape hatches".
957+
# String literals inside call-like expressions to, or struct literals of those names,
958+
# are exempt from the writing system check.
959+
#
960+
# Default: []
961+
escape-hatches:
962+
- 'github.com/nicksnyder/go-i18n/v2/i18n.Message'
963+
- 'example.com/your/project/i18n/markers.Raw'
964+
- 'example.com/your/project/i18n/markers.OK'
965+
- 'example.com/your/project/i18n/markers.TODO'
966+
- 'command-line-arguments.Simple'
967+
# Ignore test files.
968+
#
969+
# Default: true
970+
ignore-tests: false
971+
# List of Unicode scripts to watch for any usage in string literals.
972+
# https://pkg.go.dev/unicode#pkg-variables
973+
#
974+
# Default: ["Han"]
975+
watch-for-scripts:
976+
- Devanagari
977+
- Han
978+
- Hangul
979+
- Hiragana
980+
- Katakana
981+
951982
govet:
952983
# Report about shadowed variables.
953984
# Default: false
@@ -2049,6 +2080,7 @@ linters:
20492080
- goprintffuncname
20502081
- gosec
20512082
- gosimple
2083+
- gosmopolitan
20522084
- govet
20532085
- grouper
20542086
- ifshort
@@ -2159,6 +2191,7 @@ linters:
21592191
- goprintffuncname
21602192
- gosec
21612193
- gosimple
2194+
- gosmopolitan
21622195
- govet
21632196
- grouper
21642197
- ifshort

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ require (
106106
github.com/ultraware/whitespace v0.0.5
107107
github.com/uudashr/gocognit v1.0.6
108108
github.com/valyala/quicktemplate v1.7.0
109+
github.com/xen0n/gosmopolitan v1.2.1
109110
github.com/yagipy/maintidx v1.0.0
110111
github.com/yeya24/promlinter v0.2.0
111112
gitlab.com/bosi/decorder v0.2.3

go.sum

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/config/linters_settings.go

+14
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ var defaultLintersSettings = LintersSettings{
6161
Gosec: GoSecSettings{
6262
Concurrency: runtime.NumCPU(),
6363
},
64+
Gosmopolitan: GosmopolitanSettings{
65+
AllowTimeLocal: false,
66+
EscapeHatches: []string{},
67+
IgnoreTests: true,
68+
WatchForScripts: []string{"Han"},
69+
},
6470
Ifshort: IfshortSettings{
6571
MaxDeclLines: 1,
6672
MaxDeclChars: 30,
@@ -167,6 +173,7 @@ type LintersSettings struct {
167173
Gomodguard GoModGuardSettings
168174
Gosec GoSecSettings
169175
Gosimple StaticCheckSettings
176+
Gosmopolitan GosmopolitanSettings
170177
Govet GovetSettings
171178
Grouper GrouperSettings
172179
Ifshort IfshortSettings
@@ -449,6 +456,13 @@ type GoSecSettings struct {
449456
Concurrency int `mapstructure:"concurrency"`
450457
}
451458

459+
type GosmopolitanSettings struct {
460+
AllowTimeLocal bool `mapstructure:"allow-time-local"`
461+
EscapeHatches []string `mapstructure:"escape-hatches"`
462+
IgnoreTests bool `mapstructure:"ignore-tests"`
463+
WatchForScripts []string `mapstructure:"watch-for-scripts"`
464+
}
465+
452466
type GovetSettings struct {
453467
Go string `mapstructure:"-"`
454468
CheckShadowing bool `mapstructure:"check-shadowing"`

pkg/golinters/gosmopolitan.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package golinters
2+
3+
import (
4+
"strings"
5+
6+
"github.com/xen0n/gosmopolitan"
7+
"golang.org/x/tools/go/analysis"
8+
9+
"github.com/golangci/golangci-lint/pkg/config"
10+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
11+
)
12+
13+
func NewGosmopolitan(s *config.GosmopolitanSettings) *goanalysis.Linter {
14+
a := gosmopolitan.NewAnalyzer()
15+
16+
cfgMap := map[string]map[string]interface{}{}
17+
if s != nil {
18+
cfgMap[a.Name] = map[string]interface{}{
19+
"allowtimelocal": s.AllowTimeLocal,
20+
"escapehatches": strings.Join(s.EscapeHatches, ","),
21+
"lookattests": !s.IgnoreTests,
22+
"watchforscripts": strings.Join(s.WatchForScripts, ","),
23+
}
24+
}
25+
26+
return goanalysis.NewLinter(
27+
a.Name,
28+
a.Doc,
29+
[]*analysis.Analyzer{a},
30+
cfgMap,
31+
).WithLoadMode(goanalysis.LoadModeTypesInfo)
32+
}

pkg/lint/lintersdb/manager.go

+8
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
135135
gomodguardCfg *config.GoModGuardSettings
136136
gosecCfg *config.GoSecSettings
137137
gosimpleCfg *config.StaticCheckSettings
138+
gosmopolitanCfg *config.GosmopolitanSettings
138139
govetCfg *config.GovetSettings
139140
grouperCfg *config.GrouperSettings
140141
ifshortCfg *config.IfshortSettings
@@ -213,6 +214,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
213214
gomodguardCfg = &m.cfg.LintersSettings.Gomodguard
214215
gosecCfg = &m.cfg.LintersSettings.Gosec
215216
gosimpleCfg = &m.cfg.LintersSettings.Gosimple
217+
gosmopolitanCfg = &m.cfg.LintersSettings.Gosmopolitan
216218
govetCfg = &m.cfg.LintersSettings.Govet
217219
grouperCfg = &m.cfg.LintersSettings.Grouper
218220
ifshortCfg = &m.cfg.LintersSettings.Ifshort
@@ -558,6 +560,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
558560
WithAlternativeNames(megacheckName).
559561
WithURL("https://github.com/dominikh/go-tools/tree/master/simple"),
560562

563+
linter.NewConfig(golinters.NewGosmopolitan(gosmopolitanCfg)).
564+
WithSince("v1.53.0").
565+
WithLoadForGoAnalysis().
566+
WithPresets(linter.PresetBugs).
567+
WithURL("https://github.com/xen0n/gosmopolitan"),
568+
561569
linter.NewConfig(golinters.NewGovet(govetCfg)).
562570
WithSince("v1.0.0").
563571
WithLoadForGoAnalysis().
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
linters-settings:
2+
gosmopolitan:
3+
allow-time-local: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
linters-settings:
2+
gosmopolitan:
3+
ignore-tests: false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
linters-settings:
2+
gosmopolitan:
3+
escape-hatches:
4+
- 'command-line-arguments.A'
5+
- 'command-line-arguments.B'
6+
- 'command-line-arguments.C'
7+
- 'command-line-arguments.D'
8+
- 'fmt.Println'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
linters-settings:
2+
gosmopolitan:
3+
watch-for-scripts:
4+
- Hiragana
5+
- Katakana
6+
- Latin

test/testdata/gosmopolitan.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//golangcitest:args -Egosmopolitan
2+
package testdata
3+
4+
import (
5+
"fmt"
6+
"time"
7+
)
8+
9+
type col struct {
10+
// struct tag should not get reported
11+
Foo string `gorm:"column:bar;not null;comment:'不应该报告这一行'"`
12+
}
13+
14+
func main() {
15+
fmt.Println("hello world")
16+
fmt.Println("你好,世界") // want `string literal contains rune in Han script`
17+
fmt.Println("こんにちは、セカイ")
18+
19+
_ = col{Foo: "hello"}
20+
_ = col{Foo: "你好"} // want `string literal contains rune in Han script`
21+
22+
x := time.Local // want `usage of time.Local`
23+
_ = time.Now().In(x)
24+
_ = time.Date(2023, 1, 2, 3, 4, 5, 678901234, time.Local) // want `usage of time.Local`
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//golangcitest:args -Egosmopolitan
2+
//golangcitest:config_path testdata/configs/gosmopolitan_allow_time_local.yml
3+
//golangcitest:expected_exitcode 0
4+
package testdata
5+
6+
import (
7+
"time"
8+
)
9+
10+
func main() {
11+
_ = time.Local
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//golangcitest:args -Egosmopolitan
2+
//golangcitest:config_path testdata/configs/gosmopolitan_dont_ignore_tests.yml
3+
package testdata
4+
5+
import (
6+
"time"
7+
)
8+
9+
func main() {
10+
_ = "开启检查测试文件" // want `string literal contains rune in Han script`
11+
_ = time.Local // want `usage of time.Local`
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//golangcitest:args -Egosmopolitan
2+
//golangcitest:config_path testdata/configs/gosmopolitan_escape_hatches.yml
3+
package testdata
4+
5+
import (
6+
myAlias "fmt"
7+
)
8+
9+
type A string
10+
type B = string
11+
type C struct {
12+
foo string
13+
Bar string
14+
}
15+
16+
func D(fmt string) string {
17+
myAlias.Println(fmt, "测试")
18+
return myAlias.Sprintf("%s 测试", fmt) // want `string literal contains rune in Han script`
19+
}
20+
21+
type X struct {
22+
baz string
23+
}
24+
25+
func main() {
26+
_ = A("测试")
27+
_ = string(A(string("测试")))
28+
_ = B("测试")
29+
_ = C{
30+
foo: "测试",
31+
Bar: "测试",
32+
}
33+
_ = D("测试")
34+
35+
_ = &X{
36+
baz: "测试", // want `string literal contains rune in Han script`
37+
}
38+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//golangcitest:args -Egosmopolitan
2+
//golangcitest:expected_exitcode 0
3+
package testdata
4+
5+
import (
6+
"time"
7+
)
8+
9+
func main() {
10+
_ = "默认不检查测试文件"
11+
_ = time.Local
12+
}

test/testdata/gosmopolitan_scripts.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//golangcitest:args -Egosmopolitan
2+
//golangcitest:config_path testdata/configs/gosmopolitan_scripts.yml
3+
package testdata
4+
5+
import (
6+
"fmt"
7+
)
8+
9+
func main() {
10+
fmt.Println("hello world") // want `string literal contains rune in Latin script`
11+
fmt.Println("should not report this line") //nolint:gosmopolitan
12+
fmt.Println("你好,世界")
13+
fmt.Println("こんにちは、セカイ") // want `string literal contains rune in Hiragana script`
14+
}

0 commit comments

Comments
 (0)