diff --git a/.golangci.yml b/.golangci.yml index 920135d..ea380eb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,45 +8,53 @@ skip-dirs: linters: disable-all: true enable: - - deadcode - - errcheck - - gosimple - - govet - - ineffassign - - staticcheck - - structcheck - - typecheck - - unused - - varcheck + - asciicheck - bodyclose + - cyclop - dogsled - - dupl - - funlen + - durationcheck + - errcheck + - errname + - errorlint + - exhaustive + - exportloopref + - exportloopref - gochecknoinits + - gocognit - goconst - gocritic - gocyclo - godot + - goerr113 - gofmt - gofumpt - goimports - - golint - - gomnd - - gomodguard - goprintffuncname - gosec + - gosimple + - govet + - importas + - ineffassign - lll - - maligned - misspell - nakedret - nestif + - noctx + - nolintlint - prealloc + - revive - rowserrcheck - - scopelint + - sqlclosecheck + - sqlclosecheck + - staticcheck - stylecheck + - typecheck - unconvert - unparam + - unused + - wastedassign - whitespace + - wrapcheck linters-settings: godot: @@ -54,13 +62,19 @@ linters-settings: issues: exclude-use-default: false + exclude: + - "do not define dynamic errors, use wrapped static errors instead" exclude-rules: - path: _test\.go linters: - dupl - errcheck - funlen + - gocognit + - cyclop - gosec - - path: cmd/godot/main\.go + - noctx + - path: main\.go linters: + - cyclop - gomnd diff --git a/checks.go b/checks.go index 3cc6f88..07e3bb4 100644 --- a/checks.go +++ b/checks.go @@ -21,7 +21,7 @@ var ( // Abbreviations to exclude from capital letters check. abbreviations = []string{"i.e.", "i. e.", "e.g.", "e. g.", "etc."} - // Special tags in comments like "// nolint:", or "// +k8s:". + // Special tags in comments like "//nolint:", or "//+k8s:". tags = regexp.MustCompile(`^\+?[a-z0-9]+:`) // Special hashtags in comments like "// #nosec". @@ -40,7 +40,7 @@ type position struct { // checkComments checks every comment accordings to the rules from // `settings` argument. func checkComments(comments []comment, settings Settings) []Issue { - var issues []Issue // nolint: prealloc + var issues []Issue for _, c := range comments { if settings.Period { if iss := checkCommentForPeriod(c); iss != nil { diff --git a/cmd/godot/main.go b/cmd/godot/main.go index b411244..1b9ac67 100644 --- a/cmd/godot/main.go +++ b/cmd/godot/main.go @@ -1,3 +1,5 @@ +// godot is a linter that checks if all top-level comments contain a +// period at the end of the last sentence if needed. package main import ( @@ -24,7 +26,6 @@ var defaultSettings = godot.Settings{ Capital: false, } -// nolint: lll const usage = `Usage: godot [OPTION] [FILES] Options: @@ -34,7 +35,7 @@ Options: -h, --help show this message -v, --version show version` -// nolint:maligned +//nolint:maligned type arguments struct { config string fix bool @@ -44,7 +45,7 @@ type arguments struct { version bool } -// nolint: funlen +//nolint:funlen func main() { // Read command line arguments args, err := readArgs() @@ -112,7 +113,7 @@ func main() { } func readArgs() (args arguments, err error) { - if len(os.Args) < 2 { // nolint: gomnd + if len(os.Args) < 2 { //nolint:gomnd return arguments{}, fmt.Errorf("not enough arguments") } @@ -120,7 +121,7 @@ func readArgs() (args arguments, err error) { input := make([]string, 0, len(os.Args)-1) for i := 1; i < len(os.Args); i++ { splitted := strings.Split(os.Args[i], "=") - if len(splitted) > 2 { // nolint: gomnd + if len(splitted) > 2 { //nolint:gomnd return arguments{}, fmt.Errorf("invalid argument '%s'", os.Args[i]) } input = append(input, splitted...) @@ -172,15 +173,15 @@ func getSettings(file string) (godot.Settings, error) { file = defaultConfigFile } - data, err := os.ReadFile(file) // nolint: gosec + data, err := os.ReadFile(file) //nolint:gosec if err != nil { return godot.Settings{}, fmt.Errorf( - "read config file %s: %v", defaultConfigFile, err, + "read config file %s: %w", defaultConfigFile, err, ) } if err := yaml.Unmarshal(data, &settings); err != nil { return godot.Settings{}, fmt.Errorf( - "parse config file %s: %v", defaultConfigFile, err, + "parse config file %s: %w", defaultConfigFile, err, ) } return settings, nil diff --git a/getters.go b/getters.go index 1a47c82..1c059ef 100644 --- a/getters.go +++ b/getters.go @@ -44,7 +44,7 @@ func newParsedFile(file *ast.File, fset *token.FileSet) (*parsedFile, error) { // from "go/format" won't help here if the original file is not gofmt-ed. pf.lines, err = readFile(file, fset) if err != nil { - return nil, fmt.Errorf("read file: %v", err) + return nil, fmt.Errorf("read file: %w", err) } // Dirty hack. For some cases Go generates temporary files during @@ -82,7 +82,7 @@ func (pf *parsedFile) getComments(scope Scope, exclude []*regexp.Regexp) []comme pf.getBlockComments(exclude), pf.getTopLevelComments(exclude)..., ) - default: + case DeclScope: // Top level declaration comments and comments from the inside // of top level blocks comments = append(pf.getBlockComments(exclude), decl...) @@ -118,7 +118,7 @@ func (pf *parsedFile) getBlockComments(exclude []*regexp.Regexp) []comment { // Skip comments that are not top-level for this block // (the block itself is top level, so comments inside this block // would be on column 2) - // nolint: gomnd + //nolint:gomnd if pf.fset.Position(c.Pos()).Column != 2 { continue } @@ -136,7 +136,7 @@ func (pf *parsedFile) getBlockComments(exclude []*regexp.Regexp) []comment { // getTopLevelComments gets all top level comments. func (pf *parsedFile) getTopLevelComments(exclude []*regexp.Regexp) []comment { - var comments []comment // nolint: prealloc + var comments []comment //nolint:prealloc for _, c := range pf.file.Comments { if c == nil || len(c.List) == 0 { continue @@ -157,7 +157,7 @@ func (pf *parsedFile) getTopLevelComments(exclude []*regexp.Regexp) []comment { // getDeclarationComments gets top level declaration comments. func (pf *parsedFile) getDeclarationComments(exclude []*regexp.Regexp) []comment { - var comments []comment // nolint: prealloc + var comments []comment //nolint:prealloc for _, decl := range pf.file.Decls { var cg *ast.CommentGroup switch d := decl.(type) { @@ -184,7 +184,7 @@ func (pf *parsedFile) getDeclarationComments(exclude []*regexp.Regexp) []comment // getAllComments gets every single comment from the file. func (pf *parsedFile) getAllComments(exclude []*regexp.Regexp) []comment { - var comments []comment //nolint: prealloc + var comments []comment //nolint:prealloc for _, c := range pf.file.Comments { if c == nil || len(c.List) == 0 { continue @@ -205,6 +205,8 @@ func (pf *parsedFile) getAllComments(exclude []*regexp.Regexp) []comment { // special lines (e.g., tags or indented code examples), they are replaced // with `specialReplacer` to skip checks for them. // The result can be multiline. +// +//nolint:cyclop func getText(comment *ast.CommentGroup, exclude []*regexp.Regexp) (s string) { if len(comment.List) == 1 && strings.HasPrefix(comment.List[0].Text, "/*") && @@ -246,7 +248,7 @@ func readFile(file *ast.File, fset *token.FileSet) ([]string, error) { fname := fset.File(file.Package) f, err := os.ReadFile(fname.Name()) if err != nil { - return nil, err + return nil, err //nolint:wrapcheck } return strings.Split(string(f), "\n"), nil } diff --git a/getters_test.go b/getters_test.go index d2e3f4d..07b6b0a 100644 --- a/getters_test.go +++ b/getters_test.go @@ -1,6 +1,7 @@ package godot import ( + "errors" "go/ast" "go/parser" "go/token" @@ -82,7 +83,7 @@ func TestGetComments(t *testing.T) { if pf != nil { t.Fatalf("Unexpected file content") } - if err != errUnsuitableInput { + if !errors.Is(err, errUnsuitableInput) { t.Fatalf( "Unexpected error:\n expected: %v\n got: %v", errUnsuitableInput, err, diff --git a/godot.go b/godot.go index 7001aa0..e825e9a 100644 --- a/godot.go +++ b/godot.go @@ -3,6 +3,7 @@ package godot import ( + "errors" "fmt" "go/ast" "go/token" @@ -37,18 +38,18 @@ type comment struct { // Run runs this linter on the provided code. func Run(file *ast.File, fset *token.FileSet, settings Settings) ([]Issue, error) { pf, err := newParsedFile(file, fset) - if err == errEmptyInput || err == errUnsuitableInput { + if errors.Is(err, errEmptyInput) || errors.Is(err, errUnsuitableInput) { return nil, nil } if err != nil { - return nil, fmt.Errorf("parse input file: %v", err) + return nil, fmt.Errorf("parse input file: %w", err) } exclude := make([]*regexp.Regexp, len(settings.Exclude)) for i := 0; i < len(settings.Exclude); i++ { exclude[i], err = regexp.Compile(settings.Exclude[i]) if err != nil { - return nil, fmt.Errorf("invalid regexp: %v", err) + return nil, fmt.Errorf("invalid regexp: %w", err) } } @@ -62,9 +63,9 @@ func Run(file *ast.File, fset *token.FileSet, settings Settings) ([]Issue, error // Fix fixes all issues and returns new version of file content. func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([]byte, error) { // Read file - content, err := os.ReadFile(path) // nolint: gosec + content, err := os.ReadFile(path) //nolint:gosec if err != nil { - return nil, fmt.Errorf("read file: %v", err) + return nil, fmt.Errorf("read file: %w", err) } if len(content) == 0 { return nil, nil @@ -72,7 +73,7 @@ func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([ issues, err := Run(file, fset, settings) if err != nil { - return nil, fmt.Errorf("run linter: %v", err) + return nil, fmt.Errorf("run linter: %w", err) } // slice -> map @@ -99,17 +100,17 @@ func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([ func Replace(path string, file *ast.File, fset *token.FileSet, settings Settings) error { info, err := os.Stat(path) if err != nil { - return fmt.Errorf("check file: %v", err) + return fmt.Errorf("check file: %w", err) } mode := info.Mode() fixed, err := Fix(path, file, fset, settings) if err != nil { - return fmt.Errorf("fix issues: %v", err) + return fmt.Errorf("fix issues: %w", err) } if err := os.WriteFile(path, fixed, mode); err != nil { - return fmt.Errorf("write file: %v", err) + return fmt.Errorf("write file: %w", err) } return nil } diff --git a/godot_test.go b/godot_test.go index 5f42ada..17058d8 100644 --- a/godot_test.go +++ b/godot_test.go @@ -185,7 +185,7 @@ func TestFix(t *testing.T) { if err != nil { t.Fatalf("Failed to parse file %s: %v", testFile, err) } - content, err := os.ReadFile(testFile) // nolint: gosec + content, err := os.ReadFile(testFile) if err != nil { t.Fatalf("Failed to read test file %s: %v", testFile, err) } @@ -334,7 +334,7 @@ func TestReplace(t *testing.T) { t.Fatalf("Failed to check test file %s: %v", testFile, err) } mode := info.Mode() - content, err := os.ReadFile(testFile) // nolint: gosec + content, err := os.ReadFile(testFile) if err != nil { t.Fatalf("Failed to read test file %s: %v", testFile, err) } @@ -352,7 +352,7 @@ func TestReplace(t *testing.T) { t.Run("scope: decl", func(t *testing.T) { defer func() { - os.WriteFile(testFile, content, mode) // nolint: errcheck,gosec + os.WriteFile(testFile, content, mode) }() expected := strings.ReplaceAll(string(content), "[PERIOD_DECL]", "[PERIOD_DECL].") expected = strings.ReplaceAll(expected, "non-capital-decl", "Non-capital-decl") @@ -366,7 +366,7 @@ func TestReplace(t *testing.T) { if err != nil { t.Fatalf("Unexpected error: %v", err) } - fixed, err := os.ReadFile(testFile) // nolint: gosec + fixed, err := os.ReadFile(testFile) if err != nil { t.Fatalf("Failed to read fixed file %s: %v", testFile, err) } @@ -376,7 +376,7 @@ func TestReplace(t *testing.T) { t.Run("scope: top", func(t *testing.T) { defer func() { - os.WriteFile(testFile, content, mode) // nolint: errcheck,gosec + os.WriteFile(testFile, content, mode) }() expected := strings.ReplaceAll(string(content), "[PERIOD_DECL]", "[PERIOD_DECL].") expected = strings.ReplaceAll(expected, "[PERIOD_TOP]", "[PERIOD_TOP].") @@ -392,7 +392,7 @@ func TestReplace(t *testing.T) { if err != nil { t.Fatalf("Unexpected error: %v", err) } - fixed, err := os.ReadFile(testFile) // nolint: gosec + fixed, err := os.ReadFile(testFile) if err != nil { t.Fatalf("Failed to read fixed file %s: %v", testFile, err) } @@ -402,7 +402,7 @@ func TestReplace(t *testing.T) { t.Run("scope: all", func(t *testing.T) { defer func() { - os.WriteFile(testFile, content, mode) // nolint: errcheck,gosec + os.WriteFile(testFile, content, mode) }() expected := strings.ReplaceAll(string(content), "[PERIOD_DECL]", "[PERIOD_DECL].") expected = strings.ReplaceAll(expected, "[PERIOD_TOP]", "[PERIOD_TOP].") @@ -420,7 +420,7 @@ func TestReplace(t *testing.T) { if err != nil { t.Fatalf("Unexpected error: %v", err) } - fixed, err := os.ReadFile(testFile) // nolint: gosec + fixed, err := os.ReadFile(testFile) if err != nil { t.Fatalf("Failed to read fixed file %s: %v", testFile, err) }