diff --git a/.golangci.example.yml b/.golangci.example.yml index 50e3d6ac..c48ec722 100644 --- a/.golangci.example.yml +++ b/.golangci.example.yml @@ -151,6 +151,18 @@ linters-settings: mnd: # the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. checks: argument,case,condition,operation,return,assign + gomodguard: + allowed: + modules: # List of allowed modules + # - gopkg.in/yaml.v2 + domains: # List of allowed module domains + # - golang.org + blocked: + modules: # List of blocked modules + # - github.com/uudashr/go-module: # Blocked module + # recommendations: # Recommended modules that should be used instead (Optional) + # - golang.org/x/mod + # reason: "`mod` is the official go.mod parser library." # Reason why the recommended module should be used (Optional) govet: # report about shadowed variables check-shadowing: true diff --git a/README.md b/README.md index c75a9ac4..ae63ad0b 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,7 @@ gofmt: Gofmt checks whether code was gofmt-ed. By default this tool runs with -s goimports: Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true] golint: Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false] gomnd: An analyzer to detect magic numbers. [fast: true, auto-fix: false] +gomodguard: Allow and block list linter for direct Go module dependencies. [fast: true, auto-fix: false] goprintffuncname: Checks that printf-like functions are named with `f` at the end [fast: true, auto-fix: false] gosec (gas): Inspects source code for security problems [fast: true, auto-fix: false] interfacer: Linter that suggests narrower interface types [fast: true, auto-fix: false] @@ -487,6 +488,7 @@ golangci-lint help linters - [wsl](https://github.com/bombsimon/wsl) - Whitespace Linter - Forces you to use empty lines! - [goprintffuncname](https://github.com/jirfag/go-printf-func-name) - Checks that printf-like functions are named with `f` at the end - [gomnd](https://github.com/tommy-muehle/go-mnd) - An analyzer to detect magic numbers. +- [gomodguard](https://github.com/ryancurrah/gomodguard) - Allow and block list linter for direct Go module dependencies. - [godot](https://github.com/tetafro/godot) - Check if comments end in a period ## Configuration @@ -761,6 +763,18 @@ linters-settings: mnd: # the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. checks: argument,case,condition,operation,return,assign + gomodguard: + allowed: + modules: # List of allowed modules + # - gopkg.in/yaml.v2 + domains: # List of allowed module domains + # - golang.org + blocked: + modules: # List of blocked modules + # - github.com/uudashr/go-module: # Blocked module + # recommendations: # Recommended modules that should be used instead (Optional) + # - golang.org/x/mod + # reason: "`mod` is the official go.mod parser library." # Reason why the recommended module should be used (Optional) govet: # report about shadowed variables check-shadowing: true @@ -1264,6 +1278,7 @@ Thanks to developers and authors of used linters: - [bombsimon](https://github.com/bombsimon) - [jirfag](https://github.com/jirfag) - [tommy-muehle](https://github.com/tommy-muehle) +- [ryancurrah](https://github.com/ryancurrah) - [tetafro](https://github.com/tetafro) ## Changelog diff --git a/go.mod b/go.mod index c58df072..5fa498b3 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b github.com/pkg/errors v0.8.1 + github.com/ryancurrah/gomodguard v1.0.2 github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83 github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada // v2.19.8 github.com/sirupsen/logrus v1.4.2 diff --git a/go.sum b/go.sum index fa3ecc58..7432d940 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,7 @@ github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUD github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA= github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b h1:ekuhfTjngPhisSjOJ0QWKpPQE8/rbknHaes6WVJj5Hw= @@ -196,6 +197,7 @@ github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -215,6 +217,12 @@ github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1: github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/ryancurrah/gomodguard v1.0.0 h1:8bN8VUp6VLagAt8WpDwBuztk6axd43VjtxYDJp+3ykQ= +github.com/ryancurrah/gomodguard v1.0.0/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE= +github.com/ryancurrah/gomodguard v1.0.1 h1:bJQqszMqi0EqsxuQB55rjFQnLjOirbAL0Ww4WyT0IiM= +github.com/ryancurrah/gomodguard v1.0.1/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE= +github.com/ryancurrah/gomodguard v1.0.2 h1:vumZpZardqQ9EfFIZDNEpKaMxfqqEBMhu0uSRcDO5x4= +github.com/ryancurrah/gomodguard v1.0.2/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE= github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83 h1:AtnWoOvTioyDXFvu96MWEeE8qj4COSQnJogzLy/u41A= github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE= github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada h1:WokF3GuxBeL+n4Lk4Fa8v9mbdjlrl7bHuneF4N1bk2I= @@ -299,6 +307,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -348,6 +358,7 @@ golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200102140908-9497f49d5709 h1:AfG1EmoRkFK24HWWLxSrRKNg2G+oA3JVOG8GJsHWypQ= golang.org/x/tools v0.0.0-20200102140908-9497f49d5709/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= diff --git a/pkg/config/config.go b/pkg/config/config.go index 0c440ae9..f8775521 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -187,6 +187,18 @@ type LintersSettings struct { RowsErrCheck struct { Packages []string } + Gomodguard struct { + Allowed struct { + Modules []string `mapstructure:"modules"` + Domains []string `mapstructure:"domains"` + } `mapstructure:"allowed"` + Blocked struct { + Modules []map[string]struct { + Recommendations []string `mapstructure:"recommendations"` + Reason string `mapstructure:"reason"` + } `mapstructure:"modules"` + } `mapstructure:"blocked"` + } WSL WSLSettings Lll LllSettings diff --git a/pkg/golinters/gomodguard.go b/pkg/golinters/gomodguard.go new file mode 100644 index 00000000..aae78f81 --- /dev/null +++ b/pkg/golinters/gomodguard.go @@ -0,0 +1,88 @@ +package golinters + +import ( + "log" + "os" + "sync" + + "github.com/ryancurrah/gomodguard" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +const ( + gomodguardName = "gomodguard" +) + +// NewGomodguard returns a new Gomodguard linter. +func NewGomodguard() *goanalysis.Linter { + var ( + issues []goanalysis.Issue + mu = sync.Mutex{} + analyzer = &analysis.Analyzer{ + Name: goanalysis.TheOnlyAnalyzerName, + Doc: goanalysis.TheOnlyanalyzerDoc, + } + ) + + return goanalysis.NewLinter( + gomodguardName, + "Allow and block list linter for direct Go module dependencies.", + []*analysis.Analyzer{analyzer}, + nil, + ).WithContextSetter(func(lintCtx *linter.Context) { + analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { + var ( + files = []string{} + linterCfg = lintCtx.Cfg.LintersSettings.Gomodguard + processorCfg = gomodguard.Configuration{} + ) + processorCfg.Allowed.Modules = linterCfg.Allowed.Modules + processorCfg.Allowed.Domains = linterCfg.Allowed.Domains + for n := range linterCfg.Blocked.Modules { + for k, v := range linterCfg.Blocked.Modules[n] { + m := gomodguard.BlockedModule{k: gomodguard.Recommendations{ + Recommendations: v.Recommendations, + Reason: v.Reason, + }} + processorCfg.Blocked.Modules = append(processorCfg.Blocked.Modules, m) + break + } + } + + for _, file := range pass.Files { + files = append(files, pass.Fset.Position(file.Pos()).Filename) + } + + processor, err := gomodguard.NewProcessor(processorCfg, log.New(os.Stderr, "", 0)) + if err != nil { + lintCtx.Log.Warnf("running gomodguard failed: %s: if you are not using go modules "+ + "it is suggested to disable this linter", err) + return nil, nil + } + + gomodguardErrors := processor.ProcessFiles(files) + if len(gomodguardErrors) == 0 { + return nil, nil + } + + mu.Lock() + defer mu.Unlock() + + for _, err := range gomodguardErrors { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ //nolint:scopelint + FromLinter: gomodguardName, + Pos: err.Position, + Text: err.Reason, + }, pass)) + } + + return nil, nil + } + }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { + return issues + }).WithLoadMode(goanalysis.LoadModeSyntax) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 85f2cec7..411bf830 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -247,6 +247,10 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { linter.NewConfig(golinters.NewGoMND(m.cfg)). WithPresets(linter.PresetStyle). WithURL("https://github.com/tommy-muehle/go-mnd"), + linter.NewConfig(golinters.NewGomodguard()). + WithPresets(linter.PresetStyle). + WithLoadForGoAnalysis(). + WithURL("https://github.com/ryancurrah/gomodguard"), linter.NewConfig(golinters.NewGodot()). WithPresets(linter.PresetStyle). WithURL("https://github.com/tetafro/godot"), diff --git a/test/testdata/configs/gomodguard.yml b/test/testdata/configs/gomodguard.yml new file mode 100644 index 00000000..8f526c28 --- /dev/null +++ b/test/testdata/configs/gomodguard.yml @@ -0,0 +1,11 @@ +linters-settings: + gomodguard: + allowed: + modules: # List of allowed modules + - golang.org/x/mod/modfile + blocked: + modules: # List of blocked modules + - gopkg.in/yaml.v2: # Blocked module + recommendations: # Recommended modules that should be used instead (Optional) + - github.com/kylelemons/go-gypsy + reason: "This is an example of recommendations." # Reason why the recommended module should be used (Optional) diff --git a/test/testdata/gomodguard.go b/test/testdata/gomodguard.go new file mode 100644 index 00000000..f5f75232 --- /dev/null +++ b/test/testdata/gomodguard.go @@ -0,0 +1,27 @@ +//args: -Egomodguard +//config_path: testdata/configs/gomodguard.yml +package testdata + +import ( + "log" + + "golang.org/x/mod/modfile" + "gopkg.in/yaml.v2" // ERROR : "import of package `gopkg.in/yaml.v2` is blocked because the module is in the blocked modules list. `github.com/kylelemons/go-gypsy` is a recommended module. This is an example of recommendations." +) + +// Something just some struct +type Something struct{} + +func aAllowedImport() { // nolint: deadcode,unused + mfile, _ := modfile.Parse("go.mod", []byte{}, nil) + + log.Println(mfile) +} + +func aBlockedImport() { // nolint: deadcode,unused + data := []byte{} + something := Something{} + _ = yaml.Unmarshal(data, &something) + + log.Println(data) +}