diff --git a/.golangci.yml b/.golangci.yml index c6190f3ab9ea..ac1ed39e1464 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -31,3 +31,4 @@ linters: - maligned - prealloc - gosec + - gochecknoglobals diff --git a/README.md b/README.md index d015515884ac..bebc31dde359 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,8 @@ nakedret: Finds naked returns in functions greater than a specified function len prealloc: Finds slice declarations that could potentially be preallocated [fast: true] scopelint: Scopelint checks for unpinned variables in go programs [fast: true] gocritic: The most opinionated Go source code linter [fast: true] +gochecknoinits: Checks that no init functions are present in Go code [fast: true] +gochecknoglobals: Checks that no globals are present in Go code [fast: true] ``` Pass `-E/--enable` to enable linter and `-D/--disable` to disable: @@ -402,6 +404,8 @@ golangci-lint help linters - [prealloc](https://github.com/alexkohler/prealloc) - Finds slice declarations that could potentially be preallocated - [scopelint](https://github.com/kyoh86/scopelint) - Scopelint checks for unpinned variables in go programs - [gocritic](https://github.com/go-critic/go-critic) - The most opinionated Go source code linter +- [gochecknoinits](https://github.com/leighmcculloch/gochecknoinits) - Checks that no init functions are present in Go code +- [gochecknoglobals](https://github.com/leighmcculloch/gochecknoglobals) - Checks that no globals are present in Go code ## Configuration @@ -745,6 +749,7 @@ linters: - maligned - prealloc - gosec + - gochecknoglobals ``` ## False Positives @@ -828,6 +833,7 @@ Thanks to developers and authors of used linters: - [alexkohler](https://github.com/alexkohler) - [kyoh86](https://github.com/kyoh86) - [go-critic](https://github.com/go-critic) +- [leighmcculloch](https://github.com/leighmcculloch) ## Changelog diff --git a/pkg/golinters/gochecknoglobals.go b/pkg/golinters/gochecknoglobals.go new file mode 100644 index 000000000000..66f4b0a43afa --- /dev/null +++ b/pkg/golinters/gochecknoglobals.go @@ -0,0 +1,77 @@ +package golinters + +import ( + "context" + "fmt" + "go/ast" + "go/token" + "strings" + + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +type Gochecknoglobals struct{} + +func (Gochecknoglobals) Name() string { + return "gochecknoglobals" +} + +func (Gochecknoglobals) Desc() string { + return "Checks that no globals are present in Go code" +} + +func (lint Gochecknoglobals) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { + var res []result.Issue + for _, f := range lintCtx.ASTCache.GetAllValidFiles() { + res = append(res, lint.checkFile(f.F, f.Fset)...) + } + + return res, nil +} + +func (lint Gochecknoglobals) checkFile(f *ast.File, fset *token.FileSet) []result.Issue { + var res []result.Issue + for _, decl := range f.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + if genDecl.Tok != token.VAR { + continue + } + + for _, spec := range genDecl.Specs { + valueSpec := spec.(*ast.ValueSpec) + for _, vn := range valueSpec.Names { + if isWhitelisted(vn) { + continue + } + + res = append(res, result.Issue{ + Pos: fset.Position(genDecl.TokPos), + Text: fmt.Sprintf("%s is a global variable", formatCode(vn.Name, nil)), + FromLinter: lint.Name(), + }) + } + } + } + + return res +} + +func isWhitelisted(i *ast.Ident) bool { + return i.Name == "_" || looksLikeError(i) +} + +// looksLikeError returns true if the AST identifier starts +// with 'err' or 'Err', or false otherwise. +// +// TODO: https://github.com/leighmcculloch/gochecknoglobals/issues/5 +func looksLikeError(i *ast.Ident) bool { + prefix := "err" + if i.IsExported() { + prefix = "Err" + } + return strings.HasPrefix(i.Name, prefix) +} diff --git a/pkg/golinters/gochecknoinits.go b/pkg/golinters/gochecknoinits.go new file mode 100644 index 000000000000..6cd74decfd90 --- /dev/null +++ b/pkg/golinters/gochecknoinits.go @@ -0,0 +1,51 @@ +package golinters + +import ( + "context" + "fmt" + "go/ast" + "go/token" + + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +type Gochecknoinits struct{} + +func (Gochecknoinits) Name() string { + return "gochecknoinits" +} + +func (Gochecknoinits) Desc() string { + return "Checks that no init functions are present in Go code" +} + +func (lint Gochecknoinits) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { + var res []result.Issue + for _, f := range lintCtx.ASTCache.GetAllValidFiles() { + res = append(res, lint.checkFile(f.F, f.Fset)...) + } + + return res, nil +} + +func (lint Gochecknoinits) checkFile(f *ast.File, fset *token.FileSet) []result.Issue { + var res []result.Issue + for _, decl := range f.Decls { + funcDecl, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + + name := funcDecl.Name.Name + if name == "init" && funcDecl.Recv.NumFields() == 0 { + res = append(res, result.Issue{ + Pos: fset.Position(funcDecl.Pos()), + Text: fmt.Sprintf("don't use %s function", formatCode(name, nil)), + FromLinter: lint.Name(), + }) + } + } + + return res +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index b6e2ce366199..7c25664d249c 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -196,6 +196,14 @@ func (Manager) GetAllSupportedLinterConfigs() []linter.Config { WithSpeed(5). WithTypeInfo(). WithURL("https://github.com/go-critic/go-critic"), + linter.NewConfig(golinters.Gochecknoinits{}). + WithPresets(linter.PresetStyle). + WithSpeed(10). + WithURL("https://github.com/leighmcculloch/gochecknoinits"), + linter.NewConfig(golinters.Gochecknoglobals{}). + WithPresets(linter.PresetStyle). + WithSpeed(10). + WithURL("https://github.com/leighmcculloch/gochecknoglobals"), } isLocalRun := os.Getenv("GOLANGCI_COM_RUN") == "" diff --git a/test/testdata/gochecknoglobals.go b/test/testdata/gochecknoglobals.go new file mode 100644 index 000000000000..6a5fbc6855a0 --- /dev/null +++ b/test/testdata/gochecknoglobals.go @@ -0,0 +1,14 @@ +// args: -Egochecknoglobals +package testdata + +import ( + "errors" + "fmt" +) + +var noGlobalsVar int // ERROR "`noGlobalsVar` is a global variable" +var ErrSomeType = errors.New("test that global erorrs aren't warned") + +func NoGlobals() { + fmt.Print(noGlobalsVar) +} diff --git a/test/testdata/gochecknoinits.go b/test/testdata/gochecknoinits.go new file mode 100644 index 000000000000..a86a9ae41038 --- /dev/null +++ b/test/testdata/gochecknoinits.go @@ -0,0 +1,10 @@ +// args: -Egochecknoinits +package testdata + +import "fmt" + +func init() { // ERROR "don't use `init` function" + fmt.Println() +} + +func Init() {} diff --git a/third_party/gochecknoglobals/LICENSE b/third_party/gochecknoglobals/LICENSE new file mode 100644 index 000000000000..7e86107e5bc2 --- /dev/null +++ b/third_party/gochecknoglobals/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Leigh McCulloch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/third_party/gochecknoinits/LICENSE b/third_party/gochecknoinits/LICENSE new file mode 100644 index 000000000000..7e86107e5bc2 --- /dev/null +++ b/third_party/gochecknoinits/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Leigh McCulloch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file