From e5501b7a5a149cd4a28455afbafe4ecb56694912 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 4 Nov 2023 12:41:20 +0100 Subject: [PATCH 001/112] Initial boilerplate for rewrite --- LICENSE | 2 +- README.md | 85 +- analyzer.go | 86 +- doc/configuration.md | 329 ---- doc/rules.md | 1075 ------------- testdata/src/default_config/defer.go | 58 - testdata/src/default_config/defer.go.golden | 62 - testdata/src/default_config/doc_examples.go | 290 ---- .../src/default_config/doc_examples.go.golden | 317 ---- testdata/src/default_config/else_if.go | 20 - testdata/src/default_config/else_if.go.golden | 16 - testdata/src/default_config/fix_advanced.go | 22 - .../src/default_config/fix_advanced.go.golden | 26 - .../src/default_config/fix_cuddle_blocks.go | 62 - .../fix_cuddle_blocks.go.golden | 69 - .../src/default_config/generic_handling.go | 594 ------- .../default_config/generic_handling.go.golden | 641 -------- .../src/default_config/leading_whitespace.go | 29 + .../leading_whitespace.go.golden | 27 + testdata/src/default_config/multiline_case.go | 32 - .../default_config/multiline_case.go.golden | 30 - .../src/default_config/remove_whitespace.go | 83 - .../remove_whitespace.go.golden | 65 - testdata/src/wip/wip.go | 1 - testdata/src/wip/wip.go.golden | 1 - .../with_config/assign_and_anything/code.go | 13 - .../assign_and_anything/code.go.golden | 14 - .../src/with_config/assign_and_call/code.go | 8 - .../assign_and_call/code.go.golden | 11 - testdata/src/with_config/case_blocks/code.go | 48 - .../with_config/case_blocks/code.go.golden | 48 - testdata/src/with_config/decl/code.go | 7 - testdata/src/with_config/decl/code.go.golden | 8 - testdata/src/with_config/error_check/code.go | 9 - .../with_config/error_check/code.go.golden | 8 - .../src/with_config/multi_line_assign/code.go | 13 - .../multi_line_assign/code.go.golden | 14 - .../separate_leading_whitespace/code.go | 18 - .../code.go.golden | 17 - testdata/src/with_config/short_decl/code.go | 19 - .../src/with_config/short_decl/code.go.golden | 23 - .../src/with_config/strict_append/code.go | 9 - .../with_config/strict_append/code.go.golden | 10 - .../src/with_config/trailing_comments/code.go | 17 - .../trailing_comments/code.go.golden | 16 - wsl.go | 1425 +---------------- wsl_test.go | 86 - 47 files changed, 150 insertions(+), 5713 deletions(-) delete mode 100644 doc/configuration.md delete mode 100644 doc/rules.md delete mode 100644 testdata/src/default_config/defer.go delete mode 100644 testdata/src/default_config/defer.go.golden delete mode 100644 testdata/src/default_config/doc_examples.go delete mode 100644 testdata/src/default_config/doc_examples.go.golden delete mode 100644 testdata/src/default_config/else_if.go delete mode 100644 testdata/src/default_config/else_if.go.golden delete mode 100644 testdata/src/default_config/fix_advanced.go delete mode 100644 testdata/src/default_config/fix_advanced.go.golden delete mode 100644 testdata/src/default_config/fix_cuddle_blocks.go delete mode 100644 testdata/src/default_config/fix_cuddle_blocks.go.golden delete mode 100644 testdata/src/default_config/generic_handling.go delete mode 100644 testdata/src/default_config/generic_handling.go.golden create mode 100644 testdata/src/default_config/leading_whitespace.go create mode 100644 testdata/src/default_config/leading_whitespace.go.golden delete mode 100644 testdata/src/default_config/multiline_case.go delete mode 100644 testdata/src/default_config/multiline_case.go.golden delete mode 100644 testdata/src/default_config/remove_whitespace.go delete mode 100644 testdata/src/default_config/remove_whitespace.go.golden delete mode 100644 testdata/src/wip/wip.go delete mode 100644 testdata/src/wip/wip.go.golden delete mode 100644 testdata/src/with_config/assign_and_anything/code.go delete mode 100644 testdata/src/with_config/assign_and_anything/code.go.golden delete mode 100644 testdata/src/with_config/assign_and_call/code.go delete mode 100644 testdata/src/with_config/assign_and_call/code.go.golden delete mode 100644 testdata/src/with_config/case_blocks/code.go delete mode 100644 testdata/src/with_config/case_blocks/code.go.golden delete mode 100644 testdata/src/with_config/decl/code.go delete mode 100644 testdata/src/with_config/decl/code.go.golden delete mode 100644 testdata/src/with_config/error_check/code.go delete mode 100644 testdata/src/with_config/error_check/code.go.golden delete mode 100644 testdata/src/with_config/multi_line_assign/code.go delete mode 100644 testdata/src/with_config/multi_line_assign/code.go.golden delete mode 100644 testdata/src/with_config/separate_leading_whitespace/code.go delete mode 100644 testdata/src/with_config/separate_leading_whitespace/code.go.golden delete mode 100644 testdata/src/with_config/short_decl/code.go delete mode 100644 testdata/src/with_config/short_decl/code.go.golden delete mode 100644 testdata/src/with_config/strict_append/code.go delete mode 100644 testdata/src/with_config/strict_append/code.go.golden delete mode 100644 testdata/src/with_config/trailing_comments/code.go delete mode 100644 testdata/src/with_config/trailing_comments/code.go.golden diff --git a/LICENSE b/LICENSE index 4dade6d..4bfd049 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Simon Sawert +Copyright (c) 2018 - 2023 Simon Sawert Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 3534b70..9b5c0f6 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,21 @@ # wsl - Whitespace Linter -[![forthebadge](https://forthebadge.com/images/badges/made-with-go.svg)](https://forthebadge.com) -[![forthebadge](https://forthebadge.com/images/badges/built-with-love.svg)](https://forthebadge.com) - [![GitHub Actions](https://github.com/bombsimon/wsl/actions/workflows/go.yml/badge.svg)](https://github.com/bombsimon/wsl/actions/workflows/go.yml) [![Coverage Status](https://coveralls.io/repos/github/bombsimon/wsl/badge.svg?branch=master)](https://coveralls.io/github/bombsimon/wsl?branch=master) -`wsl` is a linter that enforces a very **non scientific** vision of how to make -code more readable by enforcing empty lines at the right places. - -**This linter is aggressive** and a lot of projects I've tested it on have -failed miserably. For this linter to be useful at all I want to be open to new -ideas, configurations and discussions! Also note that some of the warnings might -be bugs or unintentional false positives so I would love an -[issue](https://github.com/bombsimon/wsl/issues/new) to fix, discuss, change or -make something configurable! - -## Installation - -```sh -go install github.com/bombsimon/wsl/v4/cmd...@master -``` +`wsl` (**w**hite**s**pace **l**inter) is a linter that wants you to use empty +lines to separate grouping of different types to increase readability. There are +also a few places where it encourages you to _remove_ whitespaces which is at +the start and the end of blocks. ## Usage > **Note**: This linter provides a fixer that can fix most issues with the -`--fix` flag. However, currently `golangci-lint` [does not support suggested -fixes](https://github.com/golangci/golangci-lint/issues/1779) so the `--fix` -flag in `golangci-lint` will **not** work. +> `--fix` flag. However, currently `golangci-lint` [does not support suggested +> fixes][suggested-fixes-issue] so the `--fix` +> flag in `golangci-lint` will **not** work. -`wsl` uses the [analysis](https://pkg.go.dev/golang.org/x/tools/go/analysis) +`wsl` uses the [analysis] package meaning it will operate on package level with the default analysis flags and way of working. @@ -40,7 +26,7 @@ wsl [flags] wsl --allow-cuddle-declarations --fix ./... ``` -`wsl` is also integrated in [`golangci-lint`](https://golangci-lint.run) +`wsl` is also integrated in [`golangci-lint`][golangci-lint] ```sh golangci-lint run --no-config --disable-all --enable wsl @@ -48,47 +34,18 @@ golangci-lint run --no-config --disable-all --enable wsl ## Issues and configuration -The linter suppers a few ways to configure it to satisfy more than one kind of -code style. These settings could be set either with flags or with YAML -configuration if used via `golangci-lint`. - -The supported configuration can be found [in the -documentation](doc/configuration.md). +TODO -Below are the available checklist for any hit from `wsl`. If you do not see any, -feel free to raise an [issue](https://github.com/bombsimon/wsl/issues/new). +## See also -> **Note**: this linter doesn't take in consideration the issues that will be -> fixed with `go fmt -s` so ensure that the code is properly formatted before -> use. +- [`nlterutn`][nlreturn] - Use empty lines before `return` +- [`whitespace`][whitespace] - Don't use a blank newline at the start or end of + a block. +- [`gofumpt`][gofumpt] - Stricter formatter than `gofmt`. -* [Anonymous switch statements should never be cuddled](doc/rules.md#anonymous-switch-statements-should-never-be-cuddled) -* [Append only allowed to cuddle with appended value](doc/rules.md#append-only-allowed-to-cuddle-with-appended-value) -* [Assignments should only be cuddled with other assignments](doc/rules.md#assignments-should-only-be-cuddled-with-other-assignments) -* [Block should not end with a whitespace (or comment)](doc/rules.md#block-should-not-end-with-a-whitespace-or-comment) -* [Block should not start with a whitespace](doc/rules.md#block-should-not-start-with-a-whitespace) -* [Case block should end with newline at this size](doc/rules.md#case-block-should-end-with-newline-at-this-size) -* [Branch statements should not be cuddled if block has more than two lines](doc/rules.md#branch-statements-should-not-be-cuddled-if-block-has-more-than-two-lines) -* [Declarations should never be cuddled](doc/rules.md#declarations-should-never-be-cuddled) -* [Defer statements should only be cuddled with expressions on same variable](doc/rules.md#defer-statements-should-only-be-cuddled-with-expressions-on-same-variable) -* [Expressions should not be cuddled with blocks](doc/rules.md#expressions-should-not-be-cuddled-with-blocks) -* [Expressions should not be cuddled with declarations or returns](doc/rules.md#expressions-should-not-be-cuddled-with-declarations-or-returns) -* [For statement without condition should never be cuddled](doc/rules.md#for-statement-without-condition-should-never-be-cuddled) -* [For statements should only be cuddled with assignments used in the iteration](doc/rules.md#for-statements-should-only-be-cuddled-with-assignments-used-in-the-iteration) -* [Go statements can only invoke functions assigned on line above](doc/rules.md#go-statements-can-only-invoke-functions-assigned-on-line-above) -* [If statements should only be cuddled with assignments](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments) -* [If statements should only be cuddled with assignments used in the if statement itself](doc/rules.md#if-statements-should-only-be-cuddled-with-assignments-used-in-the-if-statement-itself) -* [If statements that check an error must be cuddled with the statement that assigned the error](doc/rules.md#if-statements-that-check-an-error-must-be-cuddled-with-the-statement-that-assigned-the-error) -* [Only cuddled expressions if assigning variable or using from line above](doc/rules.md#only-cuddled-expressions-if-assigning-variable-or-using-from-line-above) -* [Only one cuddle assignment allowed before defer statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-defer-statement) -* [Only one cuddle assignment allowed before for statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-for-statement) -* [Only one cuddle assignment allowed before go statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-go-statement) -* [Only one cuddle assignment allowed before if statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-if-statement) -* [Only one cuddle assignment allowed before range statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-range-statement) -* [Only one cuddle assignment allowed before switch statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-switch-statement) -* [Only one cuddle assignment allowed before type switch statement](doc/rules.md#only-one-cuddle-assignment-allowed-before-type-switch-statement) -* [Ranges should only be cuddled with assignments used in the iteration](doc/rules.md#ranges-should-only-be-cuddled-with-assignments-used-in-the-iteration) -* [Return statements should not be cuddled if block has more than two lines](doc/rules.md#return-statements-should-not-be-cuddled-if-block-has-more-than-two-lines) -* [Short declarations should cuddle only with other short declarations](doc/rules.md#short-declaration-should-cuddle-only-with-other-short-declarations) -* [Switch statements should only be cuddled with variables switched](doc/rules.md#switch-statements-should-only-be-cuddled-with-variables-switched) -* [Type switch statements should only be cuddled with variables switched](doc/rules.md#type-switch-statements-should-only-be-cuddled-with-variables-switched) + [analysis]: https://pkg.go.dev/golang.org/x/tools/go/analysis + [gofumpt]: https://github.com/mvdan/gofumpt + [golangci-lint]: https://golangci-lint.run + [nlreturn]: https://github.com/ssgreg/nlreturn + [suggested-fixes-issue]: https://github.com/golangci/golangci-lint/issues/1779 + [whitespace]: https://github.com/ultraware/whitespace diff --git a/analyzer.go b/analyzer.go index 90e9dd4..226b603 100644 --- a/analyzer.go +++ b/analyzer.go @@ -19,24 +19,6 @@ func NewAnalyzer(config *Configuration) *analysis.Analyzer { } } -func defaultConfig() *Configuration { - return &Configuration{ - AllowAssignAndAnythingCuddle: false, - AllowAssignAndCallCuddle: true, - AllowCuddleDeclaration: false, - AllowMultiLineAssignCuddle: true, - AllowSeparatedLeadingComment: false, - AllowTrailingComment: false, - ForceCuddleErrCheckAndAssign: false, - ForceExclusiveShortDeclarations: false, - StrictAppend: true, - AllowCuddleWithCalls: []string{"Lock", "RLock"}, - AllowCuddleWithRHS: []string{"Unlock", "RUnlock"}, - ErrorVariableNames: []string{"err"}, - ForceCaseTrailingWhitespaceLimit: 0, - } -} - // wslAnalyzer is a wrapper around the configuration which is used to be able to // set the configuration when creating the analyzer and later be able to update // flags and running method. @@ -47,29 +29,6 @@ type wslAnalyzer struct { func (wa *wslAnalyzer) flags() flag.FlagSet { flags := flag.NewFlagSet("", flag.ExitOnError) - // If we have a configuration set we're not running from the command line so - // we don't use any flags. - if wa.config != nil { - return *flags - } - - wa.config = defaultConfig() - - flags.BoolVar(&wa.config.AllowAssignAndAnythingCuddle, "allow-assign-and-anything", false, "Allow assignments and anything to be cuddled") - flags.BoolVar(&wa.config.AllowAssignAndCallCuddle, "allow-assign-and-call", true, "Allow assignments and calls to be cuddled (if using same variable/type)") - flags.BoolVar(&wa.config.AllowCuddleDeclaration, "allow-cuddle-declarations", false, "Allow declarations to be cuddled") - flags.BoolVar(&wa.config.AllowMultiLineAssignCuddle, "allow-multi-line-assign", true, "Allow cuddling with multi line assignments") - flags.BoolVar(&wa.config.AllowSeparatedLeadingComment, "allow-separated-leading-comment", false, "Allow empty newlines in leading comments") - flags.BoolVar(&wa.config.AllowTrailingComment, "allow-trailing-comment", false, "Allow blocks to end with a comment") - flags.BoolVar(&wa.config.ForceCuddleErrCheckAndAssign, "force-err-cuddling", false, "Force cuddling of error checks with error var assignment") - flags.BoolVar(&wa.config.ForceExclusiveShortDeclarations, "force-short-decl-cuddling", false, "Force short declarations to cuddle by themselves") - flags.BoolVar(&wa.config.StrictAppend, "strict-append", true, "Strict rules for append") - flags.IntVar(&wa.config.ForceCaseTrailingWhitespaceLimit, "force-case-trailing-whitespace", 0, "Force newlines for case blocks > this number.") - - flags.Var(&multiStringValue{slicePtr: &wa.config.AllowCuddleWithCalls}, "allow-cuddle-with-calls", "Comma separated list of idents that can have cuddles after") - flags.Var(&multiStringValue{slicePtr: &wa.config.AllowCuddleWithRHS}, "allow-cuddle-with-rhs", "Comma separated list of idents that can have cuddles before") - flags.Var(&multiStringValue{slicePtr: &wa.config.ErrorVariableNames}, "error-variable-names", "Comma separated list of error variable names") - return *flags } @@ -80,15 +39,16 @@ func (wa *wslAnalyzer) run(pass *analysis.Pass) (interface{}, error) { continue } - processor := newProcessorWithConfig(file, pass.Fset, wa.config) - processor.parseAST() + wsl := New(file, pass.Fset, wa.config) + wsl.Run() - for pos, fix := range processor.result { + for pos, fix := range wsl.Issues { textEdits := []analysis.TextEdit{} - for _, f := range fix.fixRanges { + + for _, f := range fix.FixRanges { textEdits = append(textEdits, analysis.TextEdit{ - Pos: f.fixRangeStart, - End: f.fixRangeEnd, + Pos: f.FixRangeStart, + End: f.FixRangeEnd, NewText: []byte("\n"), }) } @@ -96,7 +56,7 @@ func (wa *wslAnalyzer) run(pass *analysis.Pass) (interface{}, error) { pass.Report(analysis.Diagnostic{ Pos: pos, Category: "whitespace", - Message: fix.reason, + Message: fix.Message, SuggestedFixes: []analysis.SuggestedFix{ { TextEdits: textEdits, @@ -109,33 +69,3 @@ func (wa *wslAnalyzer) run(pass *analysis.Pass) (interface{}, error) { //nolint:nilnil // A pass don't need to return anything. return nil, nil } - -// multiStringValue is a flag that supports multiple values. It's implemented to -// contain a pointer to a string slice that will be overwritten when the flag's -// `Set` method is called. -type multiStringValue struct { - slicePtr *[]string -} - -// Set implements the flag.Value interface and will overwrite the pointer to the -// slice with a new pointer after splitting the flag by comma. -func (m *multiStringValue) Set(value string) error { - s := []string{} - - for _, v := range strings.Split(value, ",") { - s = append(s, strings.TrimSpace(v)) - } - - *m.slicePtr = s - - return nil -} - -// Set implements the flag.Value interface. -func (m *multiStringValue) String() string { - if m.slicePtr == nil { - return "" - } - - return strings.Join(*m.slicePtr, ", ") -} diff --git a/doc/configuration.md b/doc/configuration.md deleted file mode 100644 index 099a973..0000000 --- a/doc/configuration.md +++ /dev/null @@ -1,329 +0,0 @@ -# Configuration - -The configuration should always have the same name for the flag in `wsl` as the -key name in the `golangci-lint` documentation. For example if you run local -installation you should be able to use `--allow-cuddle-declarations` and if -you're using `golangci-lint` the `golangci.yaml` configuration should look like -this: - -```yaml -linters-settings: - wsl: - allow-cuddle-declarations: true -``` - -## Available settings - -The following configuration is available. Click each header to jump to the rule -description. - -### [strict-append](rules.md#append-only-allowed-to-cuddle-with-appended-value) - -Controls if the checks for slice append should be "strict" in the sense that it -will only allow these assignments to be cuddled with variables being appended. - -> Default value: true - -Required when true: - -```go -x := []string{} -y := "not going in x" - -x = append(x, "not y") -x = append(x, y) -``` - -Supported when false: - -```go -x := []string{} -y := "not going in x" -x = append(x, "not y") -z := "string" -x = append(x, "i don't care") -``` - -### [allow-assign-and-call](rules.md#only-cuddled-expressions-if-assigning-variable-or-using-from-line-above) - -Controls if you may cuddle assignments and calls without needing an empty line -between them. - -> Default value: true - -Supported when true: - -```go -x := GetX() -x.Call() -x = AssignAgain() -x.CallAgain() -``` - -Required when false: - -```go -x := GetX() -x.Call() - -x = AssignAgain() -x.CallAgain() -``` - -### [allow-assign-and-anything](rules.md#assignments-should-only-be-cuddled-with-other-assignments) - -Controls if you may cuddle assignments and anything without needing an empty line -between them. - -> Default value: false - -Supported when true: - -```go -if x == 1 { - x = 0 -} -z := x + 2 - -fmt.Println("x") -y := "x" -``` - -Required when false: - -```go -if x == 1 { - x = 0 -} - -z := x + 2 - -fmt.Println("x") - -y := "x" -``` - -### [allow-multiline-assign](rules.md#only-cuddled-expressions-if-assigning-variable-or-using-from-line-above) - -Controls if you may cuddle assignments even if they span over multiple lines. - -> Default value: true - -Supported when true: - -```go -assignment := fmt.Sprintf( - "%s%s", - "I span over", "multiple lines" -) -assignmentTwo := "and yet I may be cuddled" -assignmentThree := "this is fine" -``` - -Required when false: - -```go -assignment := fmt.Sprintf( - "%s%s", - "I span over", "multiple lines" -) - -assignmentTwo := "so I cannot be cuddled" -assignmentThree := "this is fine" -``` - -### [allow-separated-leading-comment](rules.md#block-should-not-start-with-a-whitespace) - -This option allows whitespace after each comment group that begins a block. - -> Default value: false - -For example, - -```go -func example() string { - // comment - - return fmt.Sprintf("x") -} -``` - -and - -```go -func example() string { - // comment - - // comment - return fmt.Sprintf("x") -} -``` - -become legal, as the whitespace _after_ (or between) each comment block -doesn't count against whitespace before the first actual statement. - -### [force-case-trailing-whitespace](rules.md#case-block-should-end-with-newline-at-this-size) - -Can be set to force trailing newlines at the end of case blocks to improve -readability. If the number of lines (including comments) in a case block exceeds -this number a linter error will be yielded if the case does not end with a -newline. - -> Default value: 0 (never force) - -Supported when set to 0: - -```go -switch n { -case 1: - fmt.Println("newline") - -case 2: - fmt.Println("no newline") -case 3: - fmt.Println("Many") - fmt.Println("lines") - fmt.Println("without") - fmt.Println("newlinew") -case 4: - fmt.Println("done") -} -``` - -Required when set to 2: - -```go -switch n { -case 1: - fmt.Println("must have") - fmt.Println("empty whitespace") - -case 2: - fmt.Println("might have empty whitespace") - -case 3: - fmt.Println("might skip empty whitespace") -case 4: - fmt.Println("done"9) -} -``` - -### [allow-cuddle-declarations](rules.md#declarations-should-never-be-cuddled) - -Controls if you're allowed to cuddle multiple declarations. This is false by -default to encourage you to group them in one `var` block. One major benefit -with this is that if the variables are assigned the assignments will be -tabulated. - -> Default value: false - -Supported when true: - -```go -var foo = 1 -var fooBar = 2 -``` - -Required when false: - -```go -var ( - foo = 1 - fooBar = 2 -) -``` - -### [allow-trailing-comment](rules.md#block-should-not-end-with-a-whitespace-or-comment) - -Controls if blocks can end with comments. This is not encouraged sine it's -usually code smell but might be useful do improve understanding or learning -purposes. To be allowed there must be no whitespace between the comment and the -last statement or the comment and the closing brace. - -> Default value: false - -Supported when true - -```go -if true { - fmt.Println("x") - // See ya later! -} - -if false { - fmt.Println("x") - // - // Multiline OK -} - -if 1 == 1 { - fmt.Println("x") - /* - This is also fine! - So just comment awaaay! - */ -} -``` - -### [force-err-cuddling](rules.md#if-statements-that-check-an-error-must-be-cuddled-with-the-statement-that-assigned-the-error) - -Enforces that an `if` statement checking an error variable is cuddled with the line that assigned that error variable. - -> Default value: false - -Supported when false: - -```go -err := ErrorProducingFunc() - -if err != nil { - return err -} -``` - -Required when true: - -```go -err := ErrorProducingFunc() -if err != nil { - return err -} -``` - -### [force-short-decl-cuddling](rules.md#short-declaration-should-cuddle-only-with-other-short-declarations) - -Enforces that an assignment which is actually a short declaration (using `:=`) -is only allowed to cuddle with other short declarations, and not plain -assignments, blocks, etc. This rule helps make declarations stand out by -themselves, much the same as grouping `var` statements. - -> Default value: false - -Supported when false: - -```go -a := 1 -a = 2 - -err := ErrorProducingFunc() -if err != nil { - return err -} -``` - -Required when true: - -```go -a := 1 - -a = 2 - -err := ErrorProducingFunc() - -if err != nil { - return err -} -``` - -**Note**: this means the option _overrides_ the -[force-err-cuddling](#force-err-cuddling) option above, among others. diff --git a/doc/rules.md b/doc/rules.md deleted file mode 100644 index 059bb7a..0000000 --- a/doc/rules.md +++ /dev/null @@ -1,1075 +0,0 @@ -# Whitespace Linter (`wsl`) documentation - -This page describes checks supported by [wsl](https://github.com/bombsimon/wsl) -linter and how they should be resolved or configured to handle. - -## Checklist - -### Anonymous switch statements should never be cuddled - -Anonymous `switch` statements (mindless `switch`) should deserve its needed -attention that it does not need any assigned variables. Hence, it should not -cuddle with anything before it. One bad example is: - -```go -timeout := 10 -switch { -case s.Switches["bad timeout"]: - timeout = 100 -case s.Switches["zero timeout"]: - timeout = 0 -} -``` - -#### Recommended amendment - -Add an empty line before the `switch` statement: - -```go -timeout := 10 - -switch { -case s.Switches["bad timeout"]: - timeout = 100 -case s.Switches["zero timeout"]: - timeout = 0 -} -``` - ---- - -### Append only allowed to cuddle with appended value - -> Can be configured, see [configuration -documentation](configuration.md#strict-append) - -`append` is only allowed to cuddle with the appended value. Otherwise, they -deserve some distance. If the variables you're assigning isn't going to be -appended (or used in the append, e.g. a function call), try to separate it. - -```go -x := []string{} -x = append(x, "literal") -notUsed := "just assigning, don't mind me" -x = append(x, "not z..") -useMe := "right away" -alsoNotUsed := ":(" -x = append(x, "just noise" -x = append(x, useMe) -``` - -#### Recommended amendment - -Things not going to be appended should not be cuddled in the append path (or a -single append if only one). Variables being appended can be placed immediately -before an append statement. - -```go -notUsed := "just assigning, don't mind me" -alsoNotUsed := ":(" - -x := []string{} -x = append(x, "literal") -x = append(x, "not z..") -x = append(x, "just noise" -useMe := "to be used" -x = append(x, useMe)) -``` - ---- - -### Assignments should only be cuddled with other assignments - -> Can be configured, see [configuration -documentation](configuration.md#allow-assign-and-anything) - -Assignments should either be grouped together or have some space between whoever -else before it. One bad example is `z` and `y` in such case: - -```go -if x == 1 { - x = 0 -} -z := x + 2 - -fmt.Println("x") -y := "x" -``` - -#### Recommended amendment - -Group all assignments together when possible (`t` and `x`). Otherwise, leave -an empty line before the assignment (e.g. `z`). - -```go -if x == 1 { - x = 0 -} - -z := x + 2 -y := "x" - -fmt.Println("x") -``` - ---- - -### Block should not end with a whitespace (or comment) - -> Can be configured, see [configuration -documentation](configuration.md#allow-trailing-comment) - -Having an empty trailing whitespace is unnecessary and makes the block -definition looks never-ending long. You want to let reader know that the -code definitions end right after the last statement. Also, any trailing -comments should be on the top. One bad example: - -```go -func example() string { - return fmt.Sprintf("x") - // TODO: add mux function later. - -} -``` - -#### Recommended amendment - -Remove the unnecessary trailing whitespace line (after `return` statement). -Move the comment to the top. - -```go -func example(y int) string { - // TODO: add mux function later. - return fmt.Sprintf("x") -} -``` - ---- - -### Case block should end with newline at this size - -> Can be configured, see [configuration -documentation](configuration.md#force-case-trailing-whitespace) - -To improve readability WSL can force to add whitespaces based on a set limit. -See link to configuration for options. - -With `force-case-trailing-whitespace` set to 1 this yields an error. - -```go -switch n { -case 1: - fmt.Println("one") -case 2: - fmt.Println("two") -} -``` - -#### Recommended amendment - -```go -switch n { -case 1: - fmt.Println("one") - -case 2: - fmt.Println("two") -} -``` - ---- - -### Block should not start with a whitespace - -Having an empty leading whitespace is unnecessary and makes the block definition -looks disconnected and long. You want to let reader to know that the code -definitions start right after the block declaration. One bad example is: - -```go -func example() string { - - return fmt.Sprintf("x") -} -``` - -#### Recommended amendment - -Remove the unnecessary leading whitespace line (before `x` definition). - -```go -func example() string { - return fmt.Sprintf("x") -} -``` - -> However, this can be configured to allow white space after one -> or more initial comment groups, see -[configuration documentation](configuration.md#allow-separated-leading-comment) -> -> If that is done, then these examples are allowed: - -```go - func example() string { - // comment - - return fmt.Sprintf("x") -} -``` - -> and - -```go - func example() string { - // comment - - // comment - return fmt.Sprintf("x") -} -``` - ---- - -### Branch statements should not be cuddled if block has more than two lines - -Branch statements (`break`, `continue`, and `return`) should stand out clearly -when the block is having more than or equal to 2 lines. Hence, it deserves -some spacing. One bad example is: - -```go -for i := range make([]int, 5) { - if i > 2 { - sendToOne(i) - sendToSecond(i) - continue - } - - if statement == "is short" { - sendToOne(i) - break - } -} -``` - -#### Recommended amendment - -Add an empty line before the branch statements (`continue`) contained within -a more than or equal to 2 lines code block: - -```go -for i := range make([]int, 5) { - if i > 2 { - sendToOne(i) - sendToSecond(i) - - continue - } - - if statement == "is short" { - sendToOne(i) - break - } -} -``` - ---- - -### Declarations should never be cuddled - -> Can be configured, see -[configuration documentation](configuration.md#allow-cuddle-declarations) - -`var` declarations, in opinion, should never be cuddled. Instead, multiple -`var` patterns is encouraged to use the grouped `var` format. One case study is: - -```go -var eol string -var i int -``` - -#### Recommended amendment - -Since this hit is opinionated, there are 3 ways to deal with it: - -1) Use the grouped `var` pattern: - -```go -var ( - eol = "" - i = 0 -) -``` - -2) Allow it by configuration (`wsl` or `golangci-lint`) - -3) Use an empty line between them - -```go -var eol = "" - -var i = 0 -``` - ---- - -### Defer statements should only be cuddled with expressions on same variable - -`defer` statement should only cuddle with related expressions. Otherwise, it -deserves some distance from whatever it is. One bad example is: - -```go -x := 4 -defer notX() -``` - -#### Recommended amendment - -Add an empty line before `defer`: - -```go -x := 4 - -defer notX() - -thisIsX := func() {} -defer thisIsX() -``` - ---- - -### Expressions should not be cuddled with blocks - -Code expressions should not be cuddled with a block (e.g. `if` or `switch`). -There must be some clarity between the block and the new expression itself. -One bad example is: - -```go -t, err := someFunc() -if err != nil { - // handle error - return -} -fmt.Println(t) -``` - -#### Recommended amendment - -An empty line between the expression and block. - -```go -t, err := someFunc() -if err != nil { - // handle error - return -} - -fmt.Println(t) -``` - ---- - -### Expressions should not be cuddled with declarations or returns - -Any expressions should not cuddle with any declarations (`var`) or `return`. -They deserve some space for clarity. One bad example is (`run()`): - -```go -var i int -run() - -return i -run() -``` - -#### Recommended amendment - -Give an empty after the declaration (`var`) and an empty line before the -`return`: - -```go -var i int - -run() - -return i - -run() -``` - ---- - -### For statement without condition should never be cuddled - -`for` loop without conditions (infinity loop) should deserves its own -attention. Hence, it should not be cuddled with anyone. - -```go -x := "hey" -for { - fmt.Printf("x") -} -``` - -#### Recommended amendment - -Add an empty line before the `for` loop. - -```go -x := "hey" - -for { - fmt.Printf("x") -} -``` - ---- - -### For statements should only be cuddled with assignments used in the iteration - -`for` statement should only cuddle with related assignments. Otherwise, its -relationship status can be very complicated, making reader wondering what the -co-relation between the `for` block and whatever it is. One bad example is: - -```go -x := y + 2 -for i := 0; i < y+10; i++ { - fmt.Println(i) -} - -list := getList() -for i, v := range anotherList { - fmt.Println(i) -} -``` - -#### Recommended amendment - -Add an empty line before the `for` statement: - -```go -x := y + 2 - -for i := 0; i < y+10; i++ { - fmt.Printf("this is i:%v\n", i) -} - -step := 2 -for i := 0; i < y+10; i+=step { - fmt.Printf("this is i:%v\n", i) -} - -list := getList() -for i, v := range list { - fmt.Printf("%d: %s\n", i, v) -} -``` - ---- - -### Go statements can only invoke functions assigned on line above - -`go` statement deserves clarity from any nearby non-related executions. Hence, -it deserves an empty line separation before it. - -```go -thisIsFunc := func() {} -go thisIsNOTFunc() -``` - -#### Recommended amendment - -Add an empty before `go` statement. - -```go -thisIsFunc := func() {} -go thisIsFunc() - -thisIsNOTFunc := func() {} - -go callingAnotherFunc() -``` - ---- - -### If statements should only be cuddled with assignments - -`if` statement should only cuddle with one related assignment. Otherwise, it -should have a distance between `if` and whoever else is. - -```go -one := 1 -if 1 == 1 { - fmt.Println("duuh") -} -if 1 != one { - fmt.Println("i don't believe you!") -} -``` - -#### Recommended amendment - -Group that single related assignment together with the `if` block and give one -empty line before them. - -If environment is not allowed like mutex lock blocking -(e.g. `Intercept(...)`), add an empty line before the `if` block. - -```go -if 1 == 1 { - fmt.Println("duuh") -} - -one := 1 -if 1 != one { - fmt.Println("i don't believe you!") -} -``` - ---- - -### If statements should only be cuddled with assignments used in the if statement itself - -`if` statements should only cuddle with the associated assignment. Otherwise, -it deserves some space between itself and whoever before it. One bad example is -the `if` block that uses `x` cuddled with `z` assignment: - -```go -x := true -if true { - fmt.Println("didn't use cuddled variable") -} -``` - -#### Recommended amendment - -Shift the `if` block close to the assignment when possible (`if` with `x`). -Otherwise, leave an empty line before it (`if` uses `y`): - -```go -y := true - -if true { - fmt.Println("didn't use cuddled variable") -} - -x := false -if !x { - fmt.Println("proper usage of variable") -} -``` - ---- - -### Only cuddled expressions if assigning variable or using from line above - -> Can be configured, see -[configuration documentation](configuration.md#allow-assign-and-call) - -When an assignment is cuddling with an unrelated expression, they create -confusing relationship to one another. Therefore, they should keep their -distance. One bad example (all `fmt` printouts): - -```go -notInExpression := GetIt() -call.SomethingElse() -``` - -#### Recommended amendment - -Provide an empty line before the expression: - -```go -notInExpression := GetIt() - -call.SomethingElse() - -thisIsBetter := GetIt() -thisIsBetter.CallIt() -``` - ---- - -### Only one cuddle assignment allowed before defer statement - -`defer` statement should only be cuddled with 1 related assignment. If you have -more than 1 assignment(s), they should have a space between them for clarity -purposes. One bad example is: - -```go -assignmentOne := Something() -assignmentTwo := AnotherThing() -defer assignmentTwo() -``` - -> **EXCEPTION**: It is allowed to use the following: -> -> 1) The `defer` after `error` check as reported in [Issue #31](https://github.com/bombsimon/wsl/issues/31) -> -> ```go -> f1, err := os.Open("/path/to/f1.txt") -> if err != nil { -> // handle error -> return -1 -> } -> defer f1.Close() -> ``` -> -> OR -> -> 2) The conventional mutex `Lock` and `Unlock`. -> -> ```go -> m.Lock() -> defer m.Unlock() -> ``` - -#### Recommended amendment - -Add an empty line before `defer`: - -```go -assignmentOne := Something() - -assignmentTwo := AnotherThing() -defer assignmentTwo() -``` - ---- - -### Only one cuddle assignment allowed before for statement - -`for` block should only be cuddled with 1 related assignment. If you have more -than 1 assignment(s), they should have a space between them for clarity -purposes. One bad example is: - -```go -i := 0 -a := 0 -for i = 0; i < 5; i++ { - fmt.Println("x") -} -``` - -#### Recommended amendment - -An empty line between the last assignment and the `for` block. - -```go -i := 0 -a := 0 - -for i = 0; i < 5; i++ { - fmt.Println("x") -} - -j := 0 -for i = 0; j < 5; i++ { - fmt.Println("x") -} -``` - ---- - -### Only one cuddle assignment allowed before go statement - -`go` block should only be cuddled with 1 related assignment. If you have more -than 1 assignment(s), they should have a space between them for clarity -purposes. One bad example is: - -```go -assignOne := SomeOne() -assignTwo := SomeTwo() -go assignTwo() -``` - -#### Recommended amendment - -An empty line between the last assignment and the `go` block. - -```go -assignOne := SomeOne() - -assignTwo := SomeTwo() -go assignTwo() -``` - ---- - -### Only one cuddle assignment allowed before if statement - -If block should only be cuddled with 1 related assignment. If you have more -than 1 assignment(s), they should have more space between them for clarity -purposes. One bad example is: - -```go -lengthA := len(sliceA) -lengthB := len(sliceB) -if lengthA != lengthB { - fmt.Println("not equal") -} -``` - -#### Recommended amendment - -An empty line between the last assignment and the `if` block. - -```go -lengthA := len(sliceA) -lengthB := len(sliceB) - -if lengthA != lengthB { - fmt.Println("not equal") -} - -lengthC := len(sliceC) -if lengthC > 1 { - fmt.Println("only one assignment is ok") -} -``` - ---- - -### Only one cuddle assignment allowed before range statement - -`range` block should only be cuddled with 1 related assignment. If you have more -than 1 assignment(s), they should have more space between them for clarity -purposes. One bad example is: - -```go -listA := GetA() -listB := GetB() -for _, v := range listB { - fmt.Println(v) -} -``` - -#### Recommended amendment - -Give an empty line before `range` statement: - -```go -listA := GetA() -listB := GetB() - -for _, v := range listB { - fmt.Println(v) -} - -listC := GetC() -for _, v := range listC { - fmt.Println(v) -} -``` - ---- - -### Only one cuddle assignment allowed before switch statement - -`switch` block should only be cuddled with 1 related assignment. If you have -more than 1 assignment(s), they should have more space between them for clarity -purposes. One bad example is: - -```go -assignOne := SomeOone() -assignTwo := SomeTwo() -switch assignTwo { -case 1: - fmt.Println("one") -default: - fmt.Println("NOT one") -} -``` - -#### Recommended amendment - -An empty line between the last assignment and the `switch` block: - -```go -assignOne := SomeOone() -assignTwo := SomeTwo() - -switch assignTwo { -case 1: - fmt.Println("one") -default: - fmt.Println("NOT one") -} - - -assignThree := SomeThree() -switch assignTwo { -// cases -} -``` - ---- - -### Only one cuddle assignment allowed before type switch statement - -`type` `switch` block should only be cuddled with 1 related assignment. If you -have more than 1 assignment(s), they should have more space between them for -clarity purposes. - -```go -someT := GetT() -anotherT := GetT() -switch v := anotherT.(type) { -case int: - fmt.Println("was int") -default: - fmt.Println("was not int") -} -``` - -#### Recommended amendment - -An empty line between the last assignment and the `switch` block. - -```go -someT := GetT() -anotherT := GetT() - -switch v := anotherT.(type) { -case int: - fmt.Println("was int") -default: - fmt.Println("was not int") -} - -thirdT := GetT() -switch v := thirdT.(type) -// cases -} -``` - ---- - -### Ranges should only be cuddled with assignments used in the iteration - -`range` statements should only cuddle with assignments related to it. Otherwise, -it creates unrelated relationship perception that sends the reader to wonder -why are they closely together. One bad example is: - -```go -y := []string{"a", "b", "c"} - -x := 5 -for _, v := range y { - fmt.Println(v) -} -``` - -#### Recommended amendment - -Either group the related assignment together with the `range` block and -add an empty line before them (first `range`) OR an empty line before the -`range` block (second `range`): - -```go -y := []string{"a", "b", "c"} -for _, v := range y { - fmt.Println(v) -} - -// May also cuddle if used first in block -t2 := []string{} -for _, v := range y { - t2 = append(t2, v) -} -``` - ---- - -### Return statements should not be cuddled if block has more than two lines - -`return` statement should not be cuddled if the function block is not a -2-lines block. Otherwise, there should be a clarity with `return` line. If -the function block is single/double lines, the `return` statement can be -cuddled. - -```go -func F1(x int) (s string) { - switch x { - case 1: - s = "one" - case 2: - s = "two" - case 3: - s = "three" - } - return s -} -``` - -#### Recommended amendment - -An empty line between `return` and multi-line block or no empty line between -`return` and single-line block. - -```go -func F1(x int) (s string) { - switch x { - case 1: - s = "one" - case 2: - s = "two" - case 3: - s = "three" - } - - return s -} - -func PlusFifteenAsString(y int) string { - y += 15 - return fmt.Sprintf("%s", y) -} - -func IsNotZero(i int) bool { - return i != 0 -} -``` - ---- - -### Switch statements should only be cuddled with variables switched - -`switch` statements with associated switching variable should not cuddle with -non-associated switching entity. This will set the reader wondering why are -they grouped together at the first place. One bad example is: - -```go -notInSwitch := "" -switch someThingElse { -case 1: - fmt.Println("one") -case 2: - fmt.Println("one") -case 3: - fmt.Println("three") -} -``` - -#### Recommended amendment - -Group related assignment together and add an empty line before them OR add an -empty line before the `switch`: - -```go -notInSwitch := "" - -switch someThingElse { -case 1: - fmt.Println("one") -case 2: - fmt.Println("one") -case 3: - fmt.Println("three") -} - -inSwitch := 2 -switch inSwitch { -case 1: - fmt.Println("better") -} -``` - ---- - -### Type switch statements should only be cuddled with variables switched - -`type` `switch` statements should only cuddle with its switching variable. -Otherwise, it makes unclear relationship between the `switch` block and whatever -before it. Here is a bad example: - -```go -notInSwitch := GetSomeType() -switch someThingElse.(type) { -case int: - fmt.Println("int") -default: - fmt.Println("not int") -} -``` - -#### Recommended amendment - -Give an empty line before the `switch` statement: - -```go -notInSwitch := GetSomeType() - -switch someThingElse.(type) { -case int: - fmt.Println("int") -default: - fmt.Println("not int") -} - -inSwitch := GetSomeType() -switch inSwitch.(type) { -// cases -} -``` - ---- - -### If statements that check an error must be cuddled with the statement that assigned the error - -> Can be configured, see [configuration -documentation](configuration.md#force-err-cuddling) - -When an `if` statement checks a variable named `err`, which was assigned on the line above, it should be cuddled with that assignment. This makes clear the relationship between the error check and the call that may have resulted in an error. - -```go -err := ErrorProducingFunc() - -if err != nil { - return err -} -``` - -#### Recommended amendment - -Remove the empty line between the assignment and the error check. - -```go -err := ErrorProducingFunc() -if err != nil { - return err -} -``` - ---- - -### Short declaration should cuddle only with other short declarations - -> Can be configured, see [configuration -documentation](configuration.md#force-short-decl-cuddling) - -A short declaration (an "assignment" using `:=`) must not be cuddled with anything other than another short declaration. - -```go -a := 1 -err := ErrorProducingFunc() -if err != nil { - return err -} -``` - -#### Recommended amendment - -Add an empty line between the short declaration and the error check. - -```go -a := 1 -err := ErrorProducingFunc() - -if err != nil { - return err -} -``` - -**Note**: this is the opposite of the case above forcing an `err` variable -to be assigned together with its check; it also overrides some other rules -which about assignments by separating short declarations from "plain" -assignment statements using `=`. diff --git a/testdata/src/default_config/defer.go b/testdata/src/default_config/defer.go deleted file mode 100644 index 27f424d..0000000 --- a/testdata/src/default_config/defer.go +++ /dev/null @@ -1,58 +0,0 @@ -package testpkg - -import "fmt" - -func fn1() { - undoMaxProcs, err := maxprocsSet() - if err != nil { - return fmt.Errorf("failed to set GOMAXPROCS, err: %w", err) - } - defer undoMaxProcs() - - callback, x := getCb() - if x != b { - return - } - defer callback() - - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - db, err := OpenDB() - requireNoError(t, err) - defer db.Close() - - tx := BeginTx(db) - defer func() { - EndTx(tx, err) - }() - - thingOne := getOne() - thingTwo := getTwo() - defer thingOne.Close() -} - -func fn2() { - thingOne := getOne() - thingTwo := getTwo() - defer thingTwo.Close() // want "only one cuddle assignment allowed before defer statement" - - someF, err := getF() - if err != nil { - return err - } - someF() // want "expressions should not be cuddled with block" - - thingOne := getOne() - defer thingOne.Close() - thingTwo := getTwo() // want "assignments should only be cuddled with other assignments" - defer thingTwo.Close() // want "only one cuddle assignment allowed before defer statement" - - thingOne := getOne() - thingTwo := getTwo() - defer thingOne.Close() // want "only one cuddle assignment allowed before defer statement" - defer thingTwo.Close() -} diff --git a/testdata/src/default_config/defer.go.golden b/testdata/src/default_config/defer.go.golden deleted file mode 100644 index aff6e96..0000000 --- a/testdata/src/default_config/defer.go.golden +++ /dev/null @@ -1,62 +0,0 @@ -package testpkg - -import "fmt" - -func fn1() { - undoMaxProcs, err := maxprocsSet() - if err != nil { - return fmt.Errorf("failed to set GOMAXPROCS, err: %w", err) - } - defer undoMaxProcs() - - callback, x := getCb() - if x != b { - return - } - defer callback() - - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - db, err := OpenDB() - requireNoError(t, err) - defer db.Close() - - tx := BeginTx(db) - defer func() { - EndTx(tx, err) - }() - - thingOne := getOne() - thingTwo := getTwo() - defer thingOne.Close() -} - -func fn2() { - thingOne := getOne() - - thingTwo := getTwo() - defer thingTwo.Close() // want "only one cuddle assignment allowed before defer statement" - - someF, err := getF() - if err != nil { - return err - } - - someF() // want "expressions should not be cuddled with block" - - thingOne := getOne() - defer thingOne.Close() - - thingTwo := getTwo() // want "assignments should only be cuddled with other assignments" - defer thingTwo.Close() // want "only one cuddle assignment allowed before defer statement" - - thingOne := getOne() - thingTwo := getTwo() - - defer thingOne.Close() // want "only one cuddle assignment allowed before defer statement" - defer thingTwo.Close() -} diff --git a/testdata/src/default_config/doc_examples.go b/testdata/src/default_config/doc_examples.go deleted file mode 100644 index 023a7c0..0000000 --- a/testdata/src/default_config/doc_examples.go +++ /dev/null @@ -1,290 +0,0 @@ -package pkg - -import ( - "fmt" - "os" -) - -func AnonyMousStatement() { - timeout := 10 - switch { // want "anonymous switch statements should never be cuddled" - case true: - timeout = 100 - case false: - timeout = 0 - } -} - -func Append() { - x := []string{} - x = append(x, "literal") - notUsed := "just assigning, don't mind me" - x = append(x, "not z..") // want "append only allowed to cuddle with appended value" - useMe := "right away" - alsoNotUsed := ":(" - x = append(x, "just noise") // want "append only allowed to cuddle with appended value" - x = append(x, useMe) -} - -func Assignment() { - if x == 1 { - x = 0 - } - z := x + 2 // want "assignments should only be cuddled with other assignments" - - fmt.Println("x") - y := "x" // want "assignments should only be cuddled with other assignments" -} - -func TrailingWhitespaceOrComment() { - return fmt.Sprintf("x") - // REMEMBER: add mux function later. - -} // want "block should not end with a whitespace" - -// Default is for this turned off so no diagnostic. -func CaseTrailingWhitespace() { - switch n { - case 1: - fmt.Println("one") - case 2: - fmt.Println("two") - } -} - -func StartingWhitespace() { // want "block should not start with a whitespace" - - - return -} - -func BranchStatement() { - for i := range make([]int, 5) { - if i > 2 { - sendToOne(i) - sendToSecond(i) - continue // want "branch statements should not be cuddled if block has more than two lines" - } - - if statement == "is short" { - sendToOne(i) - break - } - } -} - -func Declarations() { - var eol string - var i int // want "declarations should never be cuddled" -} - -func Defer() { - x := 4 - defer notX() // want "defer statements should only be cuddled with expressions on same variable" - - thisIsX := func() {} - defer thisIsX() -} - -func ExpressionAndBlocks() { - t, err := someFunc() - if err != nil { - // handle error - return - } - fmt.Println(t) // want "expressions should not be cuddled with blocks" -} - -func ExpressionAndDeclarationOrReturn() { - var i int - run() // want "expressions should not be cuddled with declarations or returns" - - return i - run() // want "expressions should not be cuddled with declarations or returns" -} - -func ForWithoutCondition() { - x := "hey" - for { // want "for statement without condition should never be cuddled" - fmt.Printf("x") - } -} - -func For() { - x := y + 2 - for i := 0; i < y+10; i++ { // want "for statements should only be cuddled with assignments used in the iteration" - fmt.Println(i) - } - - list := getList() - for i, v := range anotherList { // want "ranges should only be cuddled with assignments used in the iteration" - fmt.Println(i) - } -} - -func Go() { - thisIsFunc := func() {} - go thisIsNOTFunc() // want "go statements can only invoke functions assigned on line above" - - thisIsFunc := func() {} - go thisIsFunc() -} - -func IfAssign() { - one := 1 - if 1 == 1 { // want "if statements should only be cuddled with assignments used in the if statement itself" - fmt.Println("duuh") - } - if 1 != one { // want "if statements should only be cuddled with assignments" - fmt.Println("i don't believe you!") - } - - two := 2 - if two != one { - fmt.Println("i don't believe you!") - } -} - -func ExpressionAndAssignment() { - notInExpression := GetIt() - call.SomethingElse() // want "only cuddled expressions if assigning variable or using from line above" - - thisIsBetter := GetIt() - thisIsBetter.CallIt() -} - -func OnlyOneDefer() { - assignmentOne := Something() - assignmentTwo := AnotherThing() - defer assignmentTwo() // want "only one cuddle assignment allowed before defer statement" - - f1, err := os.Open("/path/to/f1.txt") - if err != nil { - return -1 - } - defer f1.Close() - - m.Lock() - defer m.Unlock() -} - -func OnlyOneFor() { - i := 0 - a := 0 - for i = 0; i < 5; i++ { // want "only one cuddle assignment allowed before for statement" - fmt.Println("x") - } - - a := 0 - i := 0 - for i = 0; i < 5; i++ { // want "only one cuddle assignment allowed before for statement" - fmt.Println("x") - } -} - -func OnlyOneGo() { - assignOne := SomeOne() - assignTwo := SomeTwo() - go assignTwo() // want "only one cuddle assignment allowed before go statement" -} - -func OnlyOneIf() { - lengthA := len(sliceA) - lengthB := len(sliceB) - if lengthA != lengthB { // want "only one cuddle assignment allowed before if statement" - fmt.Println("not equal") - } -} - -func OnlyOneRange() { - listA := GetA() - listB := GetB() - for _, v := range listB { // want "only one cuddle assignment allowed before range statement" - fmt.Println(v) - } -} - -func OnlyOneSwitch() { - assignOne := SomeOone() - assignTwo := SomeTwo() - switch assignTwo { // want "only one cuddle assignment allowed before switch statement" - case 1: - fmt.Println("one") - default: - fmt.Println("NOT one") - } -} - -func OnlyOneTypeSwitch() { - someT := GetT() - anotherT := GetT() - switch v := anotherT.(type) { // want "only one cuddle assignment allowed before type switch statement" - case int: - fmt.Println("was int") - default: - fmt.Println("was not int") - } -} - -func RangeWithAssignment() { - y := []string{"a", "b", "c"} - - x := 5 - for _, v := range y { // want "ranges should only be cuddled with assignments used in the iteration" - fmt.Println(v) - } - - x := 5 - - y := []string{"a", "b", "c"} - for _, v := range y { - fmt.Println(v) - } -} - -func Return() { - _ = func() { - y += 15 - y += 15 - return fmt.Sprintf("%s", y) // want "return statements should not be cuddled if block has more than two lines" - } - - _ = func() { - y += 15 - return fmt.Sprintf("%s", y) - } -} - -func Switch() { - notInSwitch := "" - switch someThingElse { // want "switch statements should only be cuddled with variables switched" - case 1: - fmt.Println("one") - case 2: - fmt.Println("one") - case 3: - fmt.Println("three") - } - - inSwitch := 2 - switch inSwitch { - case 1: - fmt.Println("better") - } -} - -func TypeSwitch() { - notInSwitch := GetSomeType() - switch someThingElse.(type) { // want "type switch statements should only be cuddled with variables switched" - case int: - fmt.Println("int") - default: - fmt.Println("not int") - } - - inSwitch := GetSomeType() - switch inSwitch.(type) { - case int: - fmt.Println("better") - } -} diff --git a/testdata/src/default_config/doc_examples.go.golden b/testdata/src/default_config/doc_examples.go.golden deleted file mode 100644 index 7426bbb..0000000 --- a/testdata/src/default_config/doc_examples.go.golden +++ /dev/null @@ -1,317 +0,0 @@ -package pkg - -import ( - "fmt" - "os" -) - -func AnonyMousStatement() { - timeout := 10 - - switch { // want "anonymous switch statements should never be cuddled" - case true: - timeout = 100 - case false: - timeout = 0 - } -} - -func Append() { - x := []string{} - x = append(x, "literal") - notUsed := "just assigning, don't mind me" - - x = append(x, "not z..") // want "append only allowed to cuddle with appended value" - useMe := "right away" - alsoNotUsed := ":(" - - x = append(x, "just noise") // want "append only allowed to cuddle with appended value" - x = append(x, useMe) -} - -func Assignment() { - if x == 1 { - x = 0 - } - - z := x + 2 // want "assignments should only be cuddled with other assignments" - - fmt.Println("x") - - y := "x" // want "assignments should only be cuddled with other assignments" -} - -func TrailingWhitespaceOrComment() { - return fmt.Sprintf("x") - // REMEMBER: add mux function later. -} // want "block should not end with a whitespace" - -// Default is for this turned off so no diagnostic. -func CaseTrailingWhitespace() { - switch n { - case 1: - fmt.Println("one") - case 2: - fmt.Println("two") - } -} - -func StartingWhitespace() { // want "block should not start with a whitespace" - return -} - -func BranchStatement() { - for i := range make([]int, 5) { - if i > 2 { - sendToOne(i) - sendToSecond(i) - - continue // want "branch statements should not be cuddled if block has more than two lines" - } - - if statement == "is short" { - sendToOne(i) - break - } - } -} - -func Declarations() { - var eol string - - var i int // want "declarations should never be cuddled" -} - -func Defer() { - x := 4 - - defer notX() // want "defer statements should only be cuddled with expressions on same variable" - - thisIsX := func() {} - defer thisIsX() -} - -func ExpressionAndBlocks() { - t, err := someFunc() - if err != nil { - // handle error - return - } - - fmt.Println(t) // want "expressions should not be cuddled with blocks" -} - -func ExpressionAndDeclarationOrReturn() { - var i int - - run() // want "expressions should not be cuddled with declarations or returns" - - return i - - run() // want "expressions should not be cuddled with declarations or returns" -} - -func ForWithoutCondition() { - x := "hey" - - for { // want "for statement without condition should never be cuddled" - fmt.Printf("x") - } -} - -func For() { - x := y + 2 - - for i := 0; i < y+10; i++ { // want "for statements should only be cuddled with assignments used in the iteration" - fmt.Println(i) - } - - list := getList() - - for i, v := range anotherList { // want "ranges should only be cuddled with assignments used in the iteration" - fmt.Println(i) - } -} - -func Go() { - thisIsFunc := func() {} - - go thisIsNOTFunc() // want "go statements can only invoke functions assigned on line above" - - thisIsFunc := func() {} - go thisIsFunc() -} - -func IfAssign() { - one := 1 - - if 1 == 1 { // want "if statements should only be cuddled with assignments used in the if statement itself" - fmt.Println("duuh") - } - - if 1 != one { // want "if statements should only be cuddled with assignments" - fmt.Println("i don't believe you!") - } - - two := 2 - if two != one { - fmt.Println("i don't believe you!") - } -} - -func ExpressionAndAssignment() { - notInExpression := GetIt() - - call.SomethingElse() // want "only cuddled expressions if assigning variable or using from line above" - - thisIsBetter := GetIt() - thisIsBetter.CallIt() -} - -func OnlyOneDefer() { - assignmentOne := Something() - - assignmentTwo := AnotherThing() - defer assignmentTwo() // want "only one cuddle assignment allowed before defer statement" - - f1, err := os.Open("/path/to/f1.txt") - if err != nil { - return -1 - } - defer f1.Close() - - m.Lock() - defer m.Unlock() -} - -func OnlyOneFor() { - i := 0 - a := 0 - - for i = 0; i < 5; i++ { // want "only one cuddle assignment allowed before for statement" - fmt.Println("x") - } - - a := 0 - - i := 0 - for i = 0; i < 5; i++ { // want "only one cuddle assignment allowed before for statement" - fmt.Println("x") - } -} - -func OnlyOneGo() { - assignOne := SomeOne() - - assignTwo := SomeTwo() - go assignTwo() // want "only one cuddle assignment allowed before go statement" -} - -func OnlyOneIf() { - lengthA := len(sliceA) - lengthB := len(sliceB) - - if lengthA != lengthB { // want "only one cuddle assignment allowed before if statement" - fmt.Println("not equal") - } -} - -func OnlyOneRange() { - listA := GetA() - - listB := GetB() - for _, v := range listB { // want "only one cuddle assignment allowed before range statement" - fmt.Println(v) - } -} - -func OnlyOneSwitch() { - assignOne := SomeOone() - - assignTwo := SomeTwo() - switch assignTwo { // want "only one cuddle assignment allowed before switch statement" - case 1: - fmt.Println("one") - default: - fmt.Println("NOT one") - } -} - -func OnlyOneTypeSwitch() { - someT := GetT() - - anotherT := GetT() - switch v := anotherT.(type) { // want "only one cuddle assignment allowed before type switch statement" - case int: - fmt.Println("was int") - default: - fmt.Println("was not int") - } -} - -func RangeWithAssignment() { - y := []string{"a", "b", "c"} - - x := 5 - - for _, v := range y { // want "ranges should only be cuddled with assignments used in the iteration" - fmt.Println(v) - } - - x := 5 - - y := []string{"a", "b", "c"} - for _, v := range y { - fmt.Println(v) - } -} - -func Return() { - _ = func() { - y += 15 - y += 15 - - return fmt.Sprintf("%s", y) // want "return statements should not be cuddled if block has more than two lines" - } - - _ = func() { - y += 15 - return fmt.Sprintf("%s", y) - } -} - -func Switch() { - notInSwitch := "" - - switch someThingElse { // want "switch statements should only be cuddled with variables switched" - case 1: - fmt.Println("one") - case 2: - fmt.Println("one") - case 3: - fmt.Println("three") - } - - inSwitch := 2 - switch inSwitch { - case 1: - fmt.Println("better") - } -} - -func TypeSwitch() { - notInSwitch := GetSomeType() - - switch someThingElse.(type) { // want "type switch statements should only be cuddled with variables switched" - case int: - fmt.Println("int") - default: - fmt.Println("not int") - } - - inSwitch := GetSomeType() - switch inSwitch.(type) { - case int: - fmt.Println("better") - } -} diff --git a/testdata/src/default_config/else_if.go b/testdata/src/default_config/else_if.go deleted file mode 100644 index 7b10487..0000000 --- a/testdata/src/default_config/else_if.go +++ /dev/null @@ -1,20 +0,0 @@ -func fn() { - if true { // want "block should not start with a whitespace" - - fmt.Println("a") - } - - if true { - fmt.Println("a") - } else if false { // want "block should not start with a whitespace" - - fmt.Println("b") - } else { // want "block should not start with a whitespace" - - fmt.Println("c") - fmt.Println("c") - fmt.Println("c") - return // want "return statements should not be cuddled if block has more than two lines" - - } // want "block should not end with a whitespace" -} diff --git a/testdata/src/default_config/else_if.go.golden b/testdata/src/default_config/else_if.go.golden deleted file mode 100644 index 44af882..0000000 --- a/testdata/src/default_config/else_if.go.golden +++ /dev/null @@ -1,16 +0,0 @@ -func fn() { - if true { // want "block should not start with a whitespace" - fmt.Println("a") - } - - if true { - fmt.Println("a") - } else if false { // want "block should not start with a whitespace" - fmt.Println("b") - } else { // want "block should not start with a whitespace" - fmt.Println("c") - fmt.Println("c") - fmt.Println("c") - return // want "return statements should not be cuddled if block has more than two lines" - } // want "block should not end with a whitespace" -} diff --git a/testdata/src/default_config/fix_advanced.go b/testdata/src/default_config/fix_advanced.go deleted file mode 100644 index 4ba14c8..0000000 --- a/testdata/src/default_config/fix_advanced.go +++ /dev/null @@ -1,22 +0,0 @@ -package testpkg - -func Advanced() { - var foo = 1 - var bar = 2 // want "declarations should never be cuddled" - var biz int // want "declarations should never be cuddled" - - x := []string{} - x = append(x, "literal") - notUsed := "just assigning, don't mind me" - x = append(x, "not z..") // want "append only allowed to cuddle with appended value" - useMe := "right away" - alsoNotUsed := ":(" - x = append(x, "just noise") // want "append only allowed to cuddle with appended value" - x = append(x, useMe) - - _ = foo - _ = bar - _ = biz - _ = notUsed - _ = alsoNotUsed -} diff --git a/testdata/src/default_config/fix_advanced.go.golden b/testdata/src/default_config/fix_advanced.go.golden deleted file mode 100644 index eae2712..0000000 --- a/testdata/src/default_config/fix_advanced.go.golden +++ /dev/null @@ -1,26 +0,0 @@ -package testpkg - -func Advanced() { - var foo = 1 - - var bar = 2 // want "declarations should never be cuddled" - - var biz int // want "declarations should never be cuddled" - - x := []string{} - x = append(x, "literal") - notUsed := "just assigning, don't mind me" - - x = append(x, "not z..") // want "append only allowed to cuddle with appended value" - useMe := "right away" - alsoNotUsed := ":(" - - x = append(x, "just noise") // want "append only allowed to cuddle with appended value" - x = append(x, useMe) - - _ = foo - _ = bar - _ = biz - _ = notUsed - _ = alsoNotUsed -} diff --git a/testdata/src/default_config/fix_cuddle_blocks.go b/testdata/src/default_config/fix_cuddle_blocks.go deleted file mode 100644 index 0c7f1a4..0000000 --- a/testdata/src/default_config/fix_cuddle_blocks.go +++ /dev/null @@ -1,62 +0,0 @@ -package testpkg - -import "fmt" - -func FailBlocks() { - if false { - return - } - _, err := maybeErr() // want "assignments should only be cuddled with other assignments" - if err != nil { // want "only one cuddle assignment allowed before if statement" - return - } - - if true { - return - } - _, x := maybeErr() // want "assignments should only be cuddled with other assignments" - if err != nil { // want "only one cuddle assignment allowed before if statement" - return - } - - x = nil - _, err = maybeErr() - if err != nil { // want "only one cuddle assignment allowed before if statement" - panic(err) - } - - if x != nil { - } - switch 1 { // want "anonymous switch statements should never be cuddled" - case 1: - fmt.Println("one") - case 2: - fmt.Println("two") - case 3: - fmt.Println("three") - } - - foo := 1 - bar := 2 - baz := 3 - if 1 == 1 { // want "only one cuddle assignment allowed before if statement" - baz = 4 - } - - _ = foo - _ = bar - _ = baz - - a := 1 - b := 2 - for b < bar { // want "only one cuddle assignment allowed before for statement" - b-- - } - - _ = a - _ = b -} - -func maybeErr() (int, error) { - return 0, nil -} diff --git a/testdata/src/default_config/fix_cuddle_blocks.go.golden b/testdata/src/default_config/fix_cuddle_blocks.go.golden deleted file mode 100644 index 7b91d40..0000000 --- a/testdata/src/default_config/fix_cuddle_blocks.go.golden +++ /dev/null @@ -1,69 +0,0 @@ -package testpkg - -import "fmt" - -func FailBlocks() { - if false { - return - } - - _, err := maybeErr() // want "assignments should only be cuddled with other assignments" - if err != nil { // want "only one cuddle assignment allowed before if statement" - return - } - - if true { - return - } - - _, x := maybeErr() // want "assignments should only be cuddled with other assignments" - - if err != nil { // want "only one cuddle assignment allowed before if statement" - return - } - - x = nil - - _, err = maybeErr() - if err != nil { // want "only one cuddle assignment allowed before if statement" - panic(err) - } - - if x != nil { - } - - switch 1 { // want "anonymous switch statements should never be cuddled" - case 1: - fmt.Println("one") - case 2: - fmt.Println("two") - case 3: - fmt.Println("three") - } - - foo := 1 - bar := 2 - - baz := 3 - if 1 == 1 { // want "only one cuddle assignment allowed before if statement" - baz = 4 - } - - _ = foo - _ = bar - _ = baz - - a := 1 - - b := 2 - for b < bar { // want "only one cuddle assignment allowed before for statement" - b-- - } - - _ = a - _ = b -} - -func maybeErr() (int, error) { - return 0, nil -} diff --git a/testdata/src/default_config/generic_handling.go b/testdata/src/default_config/generic_handling.go deleted file mode 100644 index 418523f..0000000 --- a/testdata/src/default_config/generic_handling.go +++ /dev/null @@ -1,594 +0,0 @@ -package pkg - -import ( - "encoding/json" - "fmt" - "strconv" - "sync" - "time" -) - -func OnlyComments() { - // Also signel comments are ignored - // foo := false - // if true { - // - // foo = true - // - // } -} - -func NoBlock(r string) (x uintptr) - -func CommentOnFnLine() { // This is just a comment - fmt.Println("hello") -} - -func ReturnOnSameLine() error { - s := func() error { return nil } - _ = s -} - -func LabeledBlocks() { - goto end -end: -} - -func If() { - v, err := strconv.Atoi("1") - if err != nil { - fmt.Println(v) - } - - a := true - if a { - fmt.Println("I'm OK") - } - - a, b := true, false - if !a { - fmt.Println("I'm OK") - } - - a, b = true, false - if err != nil { // want "if statements should only be cuddled with assignments used in the if statement itself" - fmt.Println("I'm not OK") - } - - if true { - fmt.Println("I'm OK") - } - if false { // want "if statements should only be cuddled with assignments" - fmt.Println("I'm OK") - } - - c := true - fooBar := "string" - a, b := true, false - if a && b || !c && len(fooBar) > 2 { // want "only one cuddle assignment allowed before if statement" - return false - } - a, b, c := someFunc() // want "assignments should only be cuddled with other assignments" - if z && len(y) > 0 || 3 == 4 { // want "only one cuddle assignment allowed before if statement" - return true - } - - a, b := true, false - if c && b || !c && len(fooBar) > 2 { - return false - } - - r := map[string]interface{}{} - if err := json.NewDecoder(someReader).Decode(&r); err != nil { - return "this should be OK" - } -} - -func Return() { - if true { - if false { - // - } - return // want "return statements should not be cuddled if block has more than two lines" - } - - if false { - fmt.Println("this is less than two lines") - return - } - return // want "return statements should not be cuddled if block has more than two lines" -} - -func AssignmentAndDeclaration() { - if true { - fmt.Println("I'm not OK") - } - foo := true // want "assignments should only be cuddled with other assignments" - - bar := foo - baz := true - biz := false - - var a bool - fmt.Println(a) // want "expressions should not be cuddled with declarations or returns" - - b := true - fmt.Println(b) -} - -func Range() { - anotherList := make([]string, 5) - - myList := make([]int, 10) - for i := range anotherList { // want "ranges should only be cuddled with assignments used in the iteration" - fmt.Println(i) - } - - myList = make([]string, 5) - - anotherList = make([]int, 10) - for i := range anotherList { - fmt.Println(i) - } - - someList, anotherList := GetTwoListsOfStrings() - for i := range append(someList, anotherList...) { - fmt.Println(i) - } - - aThirdList := GetList() - for i := range append(someList, aThirdList...) { - fmt.Println(i) - } -} - -func FirstInBlock() { - idx := i - if i > 0 { - idx = i - 1 - } - - vals := map[int]struct{}{} - for i := range make([]int, 5) { - vals[i] = struct{}{} - } - - x := []int{} - - vals := map[int]struct{}{} - for i := range make([]int, 5) { // want "ranges should only be cuddled with assignments used in the iteration" - x = append(x, i) - } -} - -func OnlyCuddleOneAssignment() { - foo := true - bar := false - - biz := true || false - if biz { - return true - } - - foo := true - bar := false - biz := true || false - - if biz { - return true - } - - foo := true - bar := false - biz := true || false - if biz { // want "only one cuddle assignment allowed before if statement" - return false - } -} - -func IdentifiersWithIndices() { - runes := []rune{'+', '-'} - if runes[0] == '+' || runes[0] == '-' { - return string(runes[1:]) - } - - listTwo := []string{} - - listOne := []string{"one"} - if listOne[0] == "two" { - return "not allowed" - } - - for i := range make([]int, 10) { - key := GetKey() - if val, ok := someMap[key]; ok { - fmt.Println("ok!") - } - - someOtherMap := GetMap() - if val, ok := someOtherMap[key]; ok { - fmt.Println("ok") - } - - someIndex := 3 - if val := someSlice[someIndex]; val != nil { - retunr - } - } -} - -func Defer() { - thingOne := getOne() - thingTwo := getTwo() - - defer thingOne.Close() - defer thingTwo.Close() - - thingOne := getOne() - defer thingOne.Close() - - thingTwo := getTwo() - defer thingTwo.Close() - - thingOne := getOne() - defer thingOne.Close() - thingTwo := getTwo() // want "assignments should only be cuddled with other assignments" - defer thingTwo.Close() // want "only one cuddle assignment allowed before defer statement" - - thingOne := getOne() - thingTwo := getTwo() - defer thingOne.Close() // want "only one cuddle assignment allowed before defer statement" - defer thingTwo.Close() - - m := sync.Mutex{} - - m.Lock() - defer m.Unlock() - - foo := true - defer func(b bool) { // want "defer statements should only be cuddled with expressions on same variable" - fmt.Printf("%v", b) - }() -} - -func For() { - bool := true - for { // want "for statement without condition should never be cuddled" - fmt.Println("should not be allowed") - - if bool { - break - } - } -} - -func Go() { - go func() { - panic("is this real life?") - }() - - fooFunc := func() {} - go fooFunc() - - barFunc := func() {} - go fooFunc() // want "go statements can only invoke functions assigned on line above" - - go func() { - fmt.Println("hey") - }() - - cuddled := true - go func() { // want "go statements can only invoke functions assigned on line above" - fmt.Println("hey") - }() - - argToGo := 1 - go takesArg(argToGo) - - notArgToGo := 1 - go takesArg(argToGo) // want "go statements can only invoke functions assigned on line above" - - t1 := NewT() - t2 := NewT() - t3 := NewT() - - go t1() - go t2() - go t3() - - multiCuddle1 := NewT() - multiCuddle2 := NewT() - go multiCuddle2() // want "only one cuddle assignment allowed before go" - - t4 := NewT() - t5 := NewT() - go t5() // want "only one cuddle assignment allowed before go" - go t4() -} - -func Switch() { - var b bool - switch b { - case true: - return "a" - case false: - return "b" - } - - t := time.Now() - - switch { - case t.Hour() < 12: - fmt.Println("It's before noon") - default: - fmt.Println("It's after noon") - } - - var b bool - switch anotherBool { // want "switch statements should only be cuddled with variables switched" - case true: - return "a" - case false: - return "b" - } - - t := time.Now() - switch { // want "anonymous switch statements should never be cuddled" - case t.Hour() < 12: - fmt.Println("It's before noon") - default: - fmt.Println("It's after noon") - } -} - -func TypeSwitch() { - x := GetSome() - switch v := x.(type) { - case int: - return "got int" - default: - return "got other" - } - - var id string - switch i := objectID.(type) { - case int: - id = strconv.Itoa(i) - case uint32: - id = strconv.Itoa(int(i)) - case string: - id = i - } - - var b bool - switch AnotherVal.(type) { // want "type switch statements should only be cuddled with variables switched" - case int: - return "a" - case string: - return "b" - } -} - -func Append() { - someList := []string{} - - bar := "baz" - someList = append(someList, bar) - - bar := "baz" - someList = append(someList, "notBar") // want "append only allowed to cuddle with appended value" - - bar := "baz" - someList = append(someList, fmt.Sprintf("%s", bar)) - - bar := "baz" - whatever := appendFn(bar) - - s := []string{} - - s = append(s, "one") - s = append(s, "two") - s = append(s, "three") - - p.defs = append(p.defs, x) - def.parseFrom(p) - p.defs = append(p.defs, def) - def.parseFrom(p) - def.parseFrom(p) - p.defs = append(p.defs, x) -} - -func ExpressionAssignment() { - foo := true - someFunc(foo) - - foo := true - someFunc(false) // want "only cuddled expressions if assigning variable or using from line above" -} - -func Channel() { - timeoutCh := time.After(timeout) - - for range make([]int, 10) { - select { - case <-timeoutCh: - return true - case <-time.After(10 * time.Millisecond): - return false - } - } - - select { - case <-time.After(1 * time.Second): - return "1s" - default: - return "are we there yet?" - } -} - -func Branch() { - for { - if true { - singleLine := true - break - } - - if true && false { - multiLine := true - maybeVar := "var" - continue // want "branch statements should not be cuddled if block has more than two lines" - } - - if false { - multiLine := true - maybeVar := "var" - - break - } - } -} - -func Locks() { - hashFileCache.Lock() - out, ok := hashFileCache.m[file] - hashFileCache.Unlock() - - mu := &sync.Mutex{} - mu.X(y).Z.RLock() - x, y := someMap[someKey] - mu.RUnlock() -} - -func SpliceAndSlice() { - start := 0 - if v := aSlice[start:3]; v { - fmt.Println("") - } - - someKey := "str" - if v, ok := someMap[obtain(someKey)+"str"]; ok { - fmt.Println("Hey there") - } - - end := 10 - if v := arr[3:notEnd]; !v { // want "if statements should only be cuddled with assignments used in the if statement itself" - // Error - } - - notKey := "str" - if v, ok := someMap[someKey]; ok { // want "if statements should only be cuddled with assignments used in the if statement itself" - // Error - } -} - -func Block() { - var foo string - - x := func() { - var err error - foo = "1" // want "assignments should only be cuddled with other assignments" - }() - - x := func() { - var err error - foo = "1" // want "assignments should only be cuddled with other assignments" - } - - func() { - var err error - foo = "1" // want "assignments should only be cuddled with other assignments" - }() - - func() { - var err error - foo = "1" // want "assignments should only be cuddled with other assignments" - } - - func() { - func() { - return func() { - var err error - foo = "1" // want "assignments should only be cuddled with other assignments" - } - }() - } - - var x error - foo, err := func() { return "", nil } // want "assignments should only be cuddled with other assignments" - - defer func() { - var err error - foo = "1" // want "assignments should only be cuddled with other assignments" - }() - - go func() { - var err error - foo = "1" // want "assignments should only be cuddled with other assignments" - }() -} - -func CommentInLast() { - switch nextStatement.(type) { - case *someType, *anotherType: - default: - // Foo - return - } -} - -func OnlyCheckTwoLinesAboveIfAssignment() { - if err != nil { - return - } - a, err := someFn() // want "assignments should only be cuddled with other assignments" - if err != nil { // want "only one cuddle assignment allowed before if statement" - return result, err - } - b := someFn() // want "assignments should only be cuddled with other assignments" - if err := fn(b); err != nil { // want "only one cuddle assignment allowed before if statement" - return - } - - if true { - return - } - c, err := someFn() // want "assignments should only be cuddled with other assignments" - if err != nil { // want "only one cuddle assignment allowed before if statement" - return result, err - } -} - -func IncrementDecrement() { - a := 1 - a++ - a-- - b := 2 - - if true { - b-- - } - b++ // want "assignments should only be cuddled with other assignments" - - go func() {}() - b++ // want "assignments should only be cuddled with other assignments" - go func() {}() // want "only one cuddle assignment allowed before go statement" -} - -// Issue #09 -func AnonymousFunc() { - fmt.Println(func() string { - _ = 1 - _ = 2 - _ = 3 - return "string" // want "return statements should not be cuddled if block has more than two lines" - }) - - fmt.Println(func() error { - foo := "foo" - fmt.Println("fmt.Println") // want "only cuddled expressions if assigning variable or using from line above" - if foo == "bar" { // want "if statements should only be cuddled with assignments" - return fmt.Errorf("bar") - } - return nil // want "return statements should not be cuddled if block has more than two lines" - }()) -} diff --git a/testdata/src/default_config/generic_handling.go.golden b/testdata/src/default_config/generic_handling.go.golden deleted file mode 100644 index 67bc61f..0000000 --- a/testdata/src/default_config/generic_handling.go.golden +++ /dev/null @@ -1,641 +0,0 @@ -package pkg - -import ( - "encoding/json" - "fmt" - "strconv" - "sync" - "time" -) - -func OnlyComments() { - // Also signel comments are ignored - // foo := false - // if true { - // - // foo = true - // - // } -} - -func NoBlock(r string) (x uintptr) - -func CommentOnFnLine() { // This is just a comment - fmt.Println("hello") -} - -func ReturnOnSameLine() error { - s := func() error { return nil } - _ = s -} - -func LabeledBlocks() { - goto end -end: -} - -func If() { - v, err := strconv.Atoi("1") - if err != nil { - fmt.Println(v) - } - - a := true - if a { - fmt.Println("I'm OK") - } - - a, b := true, false - if !a { - fmt.Println("I'm OK") - } - - a, b = true, false - - if err != nil { // want "if statements should only be cuddled with assignments used in the if statement itself" - fmt.Println("I'm not OK") - } - - if true { - fmt.Println("I'm OK") - } - - if false { // want "if statements should only be cuddled with assignments" - fmt.Println("I'm OK") - } - - c := true - fooBar := "string" - a, b := true, false - - if a && b || !c && len(fooBar) > 2 { // want "only one cuddle assignment allowed before if statement" - return false - } - - a, b, c := someFunc() // want "assignments should only be cuddled with other assignments" - - if z && len(y) > 0 || 3 == 4 { // want "only one cuddle assignment allowed before if statement" - return true - } - - a, b := true, false - if c && b || !c && len(fooBar) > 2 { - return false - } - - r := map[string]interface{}{} - if err := json.NewDecoder(someReader).Decode(&r); err != nil { - return "this should be OK" - } -} - -func Return() { - if true { - if false { - // - } - - return // want "return statements should not be cuddled if block has more than two lines" - } - - if false { - fmt.Println("this is less than two lines") - return - } - - return // want "return statements should not be cuddled if block has more than two lines" -} - -func AssignmentAndDeclaration() { - if true { - fmt.Println("I'm not OK") - } - - foo := true // want "assignments should only be cuddled with other assignments" - - bar := foo - baz := true - biz := false - - var a bool - - fmt.Println(a) // want "expressions should not be cuddled with declarations or returns" - - b := true - fmt.Println(b) -} - -func Range() { - anotherList := make([]string, 5) - - myList := make([]int, 10) - - for i := range anotherList { // want "ranges should only be cuddled with assignments used in the iteration" - fmt.Println(i) - } - - myList = make([]string, 5) - - anotherList = make([]int, 10) - for i := range anotherList { - fmt.Println(i) - } - - someList, anotherList := GetTwoListsOfStrings() - for i := range append(someList, anotherList...) { - fmt.Println(i) - } - - aThirdList := GetList() - for i := range append(someList, aThirdList...) { - fmt.Println(i) - } -} - -func FirstInBlock() { - idx := i - if i > 0 { - idx = i - 1 - } - - vals := map[int]struct{}{} - for i := range make([]int, 5) { - vals[i] = struct{}{} - } - - x := []int{} - - vals := map[int]struct{}{} - - for i := range make([]int, 5) { // want "ranges should only be cuddled with assignments used in the iteration" - x = append(x, i) - } -} - -func OnlyCuddleOneAssignment() { - foo := true - bar := false - - biz := true || false - if biz { - return true - } - - foo := true - bar := false - biz := true || false - - if biz { - return true - } - - foo := true - bar := false - - biz := true || false - if biz { // want "only one cuddle assignment allowed before if statement" - return false - } -} - -func IdentifiersWithIndices() { - runes := []rune{'+', '-'} - if runes[0] == '+' || runes[0] == '-' { - return string(runes[1:]) - } - - listTwo := []string{} - - listOne := []string{"one"} - if listOne[0] == "two" { - return "not allowed" - } - - for i := range make([]int, 10) { - key := GetKey() - if val, ok := someMap[key]; ok { - fmt.Println("ok!") - } - - someOtherMap := GetMap() - if val, ok := someOtherMap[key]; ok { - fmt.Println("ok") - } - - someIndex := 3 - if val := someSlice[someIndex]; val != nil { - retunr - } - } -} - -func Defer() { - thingOne := getOne() - thingTwo := getTwo() - - defer thingOne.Close() - defer thingTwo.Close() - - thingOne := getOne() - defer thingOne.Close() - - thingTwo := getTwo() - defer thingTwo.Close() - - thingOne := getOne() - defer thingOne.Close() - - thingTwo := getTwo() // want "assignments should only be cuddled with other assignments" - defer thingTwo.Close() // want "only one cuddle assignment allowed before defer statement" - - thingOne := getOne() - thingTwo := getTwo() - - defer thingOne.Close() // want "only one cuddle assignment allowed before defer statement" - defer thingTwo.Close() - - m := sync.Mutex{} - - m.Lock() - defer m.Unlock() - - foo := true - - defer func(b bool) { // want "defer statements should only be cuddled with expressions on same variable" - fmt.Printf("%v", b) - }() -} - -func For() { - bool := true - - for { // want "for statement without condition should never be cuddled" - fmt.Println("should not be allowed") - - if bool { - break - } - } -} - -func Go() { - go func() { - panic("is this real life?") - }() - - fooFunc := func() {} - go fooFunc() - - barFunc := func() {} - - go fooFunc() // want "go statements can only invoke functions assigned on line above" - - go func() { - fmt.Println("hey") - }() - - cuddled := true - - go func() { // want "go statements can only invoke functions assigned on line above" - fmt.Println("hey") - }() - - argToGo := 1 - go takesArg(argToGo) - - notArgToGo := 1 - - go takesArg(argToGo) // want "go statements can only invoke functions assigned on line above" - - t1 := NewT() - t2 := NewT() - t3 := NewT() - - go t1() - go t2() - go t3() - - multiCuddle1 := NewT() - - multiCuddle2 := NewT() - go multiCuddle2() // want "only one cuddle assignment allowed before go" - - t4 := NewT() - - t5 := NewT() - go t5() // want "only one cuddle assignment allowed before go" - go t4() -} - -func Switch() { - var b bool - switch b { - case true: - return "a" - case false: - return "b" - } - - t := time.Now() - - switch { - case t.Hour() < 12: - fmt.Println("It's before noon") - default: - fmt.Println("It's after noon") - } - - var b bool - - switch anotherBool { // want "switch statements should only be cuddled with variables switched" - case true: - return "a" - case false: - return "b" - } - - t := time.Now() - - switch { // want "anonymous switch statements should never be cuddled" - case t.Hour() < 12: - fmt.Println("It's before noon") - default: - fmt.Println("It's after noon") - } -} - -func TypeSwitch() { - x := GetSome() - switch v := x.(type) { - case int: - return "got int" - default: - return "got other" - } - - var id string - switch i := objectID.(type) { - case int: - id = strconv.Itoa(i) - case uint32: - id = strconv.Itoa(int(i)) - case string: - id = i - } - - var b bool - - switch AnotherVal.(type) { // want "type switch statements should only be cuddled with variables switched" - case int: - return "a" - case string: - return "b" - } -} - -func Append() { - someList := []string{} - - bar := "baz" - someList = append(someList, bar) - - bar := "baz" - - someList = append(someList, "notBar") // want "append only allowed to cuddle with appended value" - - bar := "baz" - someList = append(someList, fmt.Sprintf("%s", bar)) - - bar := "baz" - whatever := appendFn(bar) - - s := []string{} - - s = append(s, "one") - s = append(s, "two") - s = append(s, "three") - - p.defs = append(p.defs, x) - def.parseFrom(p) - p.defs = append(p.defs, def) - def.parseFrom(p) - def.parseFrom(p) - p.defs = append(p.defs, x) -} - -func ExpressionAssignment() { - foo := true - someFunc(foo) - - foo := true - - someFunc(false) // want "only cuddled expressions if assigning variable or using from line above" -} - -func Channel() { - timeoutCh := time.After(timeout) - - for range make([]int, 10) { - select { - case <-timeoutCh: - return true - case <-time.After(10 * time.Millisecond): - return false - } - } - - select { - case <-time.After(1 * time.Second): - return "1s" - default: - return "are we there yet?" - } -} - -func Branch() { - for { - if true { - singleLine := true - break - } - - if true && false { - multiLine := true - maybeVar := "var" - - continue // want "branch statements should not be cuddled if block has more than two lines" - } - - if false { - multiLine := true - maybeVar := "var" - - break - } - } -} - -func Locks() { - hashFileCache.Lock() - out, ok := hashFileCache.m[file] - hashFileCache.Unlock() - - mu := &sync.Mutex{} - mu.X(y).Z.RLock() - x, y := someMap[someKey] - mu.RUnlock() -} - -func SpliceAndSlice() { - start := 0 - if v := aSlice[start:3]; v { - fmt.Println("") - } - - someKey := "str" - if v, ok := someMap[obtain(someKey)+"str"]; ok { - fmt.Println("Hey there") - } - - end := 10 - - if v := arr[3:notEnd]; !v { // want "if statements should only be cuddled with assignments used in the if statement itself" - // Error - } - - notKey := "str" - - if v, ok := someMap[someKey]; ok { // want "if statements should only be cuddled with assignments used in the if statement itself" - // Error - } -} - -func Block() { - var foo string - - x := func() { - var err error - - foo = "1" // want "assignments should only be cuddled with other assignments" - }() - - x := func() { - var err error - - foo = "1" // want "assignments should only be cuddled with other assignments" - } - - func() { - var err error - - foo = "1" // want "assignments should only be cuddled with other assignments" - }() - - func() { - var err error - - foo = "1" // want "assignments should only be cuddled with other assignments" - } - - func() { - func() { - return func() { - var err error - - foo = "1" // want "assignments should only be cuddled with other assignments" - } - }() - } - - var x error - - foo, err := func() { return "", nil } // want "assignments should only be cuddled with other assignments" - - defer func() { - var err error - - foo = "1" // want "assignments should only be cuddled with other assignments" - }() - - go func() { - var err error - - foo = "1" // want "assignments should only be cuddled with other assignments" - }() -} - -func CommentInLast() { - switch nextStatement.(type) { - case *someType, *anotherType: - default: - // Foo - return - } -} - -func OnlyCheckTwoLinesAboveIfAssignment() { - if err != nil { - return - } - - a, err := someFn() // want "assignments should only be cuddled with other assignments" - if err != nil { // want "only one cuddle assignment allowed before if statement" - return result, err - } - - b := someFn() // want "assignments should only be cuddled with other assignments" - if err := fn(b); err != nil { // want "only one cuddle assignment allowed before if statement" - return - } - - if true { - return - } - - c, err := someFn() // want "assignments should only be cuddled with other assignments" - if err != nil { // want "only one cuddle assignment allowed before if statement" - return result, err - } -} - -func IncrementDecrement() { - a := 1 - a++ - a-- - b := 2 - - if true { - b-- - } - - b++ // want "assignments should only be cuddled with other assignments" - - go func() {}() - - b++ // want "assignments should only be cuddled with other assignments" - - go func() {}() // want "only one cuddle assignment allowed before go statement" -} - -// Issue #09 -func AnonymousFunc() { - fmt.Println(func() string { - _ = 1 - _ = 2 - _ = 3 - - return "string" // want "return statements should not be cuddled if block has more than two lines" - }) - - fmt.Println(func() error { - foo := "foo" - - fmt.Println("fmt.Println") // want "only cuddled expressions if assigning variable or using from line above" - - if foo == "bar" { // want "if statements should only be cuddled with assignments" - return fmt.Errorf("bar") - } - - return nil // want "return statements should not be cuddled if block has more than two lines" - }()) -} diff --git a/testdata/src/default_config/leading_whitespace.go b/testdata/src/default_config/leading_whitespace.go new file mode 100644 index 0000000..97a4e62 --- /dev/null +++ b/testdata/src/default_config/leading_whitespace.go @@ -0,0 +1,29 @@ +package testpkg + +import "fmt" + +func fn1() { // want "unnecessary whitespace decreases readability" + + fmt.Println("Hello, World") +} + +func fn2() { // want "unnecessary whitespace decreases readability" + + // Space before comment. + fmt.Println("Hello, World") +} + +func fn3() { + // Space after comment - v5.0.0 BREAKING CHANGE + + fmt.Println("Hello, World") +} + +func fn4() { + fmt.Println("Hello, World") +} + +func fn5() { + // Comment without space before or after. + fmt.Println("Hello, World") +} diff --git a/testdata/src/default_config/leading_whitespace.go.golden b/testdata/src/default_config/leading_whitespace.go.golden new file mode 100644 index 0000000..360c06a --- /dev/null +++ b/testdata/src/default_config/leading_whitespace.go.golden @@ -0,0 +1,27 @@ +package testpkg + +import "fmt" + +func fn1() { // want "unnecessary whitespace decreases readability" + fmt.Println("Hello, World") +} + +func fn2() { // want "unnecessary whitespace decreases readability" + // Space before comment. + fmt.Println("Hello, World") +} + +func fn3() { + // Space after comment - v5.0.0 BREAKING CHANGE + + fmt.Println("Hello, World") +} + +func fn4() { + fmt.Println("Hello, World") +} + +func fn5() { + // Comment without space before or after. + fmt.Println("Hello, World") +} diff --git a/testdata/src/default_config/multiline_case.go b/testdata/src/default_config/multiline_case.go deleted file mode 100644 index af193c7..0000000 --- a/testdata/src/default_config/multiline_case.go +++ /dev/null @@ -1,32 +0,0 @@ -package pkg - -func Fn() { - switch { - case true, false: - fmt.Println("ok") - case true || false: - fmt.Println("ok") - case true, false: - fmt.Println("ok") - case true || false: - fmt.Println("ok") - case true, - false: // want "block should not start with a whitespace" - - fmt.Println("starting whitespace multiline case") - case true || - false: // want "block should not start with a whitespace" - - fmt.Println("starting whitespace multiline case") - case true, - false: - fmt.Println("ending whitespace multiline case") - - case true || - false: - fmt.Println("ending whitespace multiline case") - - case true, false: - fmt.Println("all") - } -} diff --git a/testdata/src/default_config/multiline_case.go.golden b/testdata/src/default_config/multiline_case.go.golden deleted file mode 100644 index 9837d6e..0000000 --- a/testdata/src/default_config/multiline_case.go.golden +++ /dev/null @@ -1,30 +0,0 @@ -package pkg - -func Fn() { - switch { - case true, false: - fmt.Println("ok") - case true || false: - fmt.Println("ok") - case true, false: - fmt.Println("ok") - case true || false: - fmt.Println("ok") - case true, - false: // want "block should not start with a whitespace" - fmt.Println("starting whitespace multiline case") - case true || - false: // want "block should not start with a whitespace" - fmt.Println("starting whitespace multiline case") - case true, - false: - fmt.Println("ending whitespace multiline case") - - case true || - false: - fmt.Println("ending whitespace multiline case") - - case true, false: - fmt.Println("all") - } -} diff --git a/testdata/src/default_config/remove_whitespace.go b/testdata/src/default_config/remove_whitespace.go deleted file mode 100644 index faf4eca..0000000 --- a/testdata/src/default_config/remove_whitespace.go +++ /dev/null @@ -1,83 +0,0 @@ -package testpkg - -import "fmt" - -func RemoveWhitespaceNoComments() { // want "block should not start with a whitespace" - - a := 1 - if a < 2 { // want "block should not start with a whitespace" - - a = 2 - - } // want "block should not end with a whitespace" - - switch { // want "block should not start with a whitespace" - - case true: - fmt.Println("true") - - default: - fmt.Println("false") - - } // want "block should not end with a whitespace" - - _ = a - -} // want "block should not end with a whitespace" - -// RemoveWhiteSpaceWithhComments keeps comments even when removing newlines. -func RemoveWhiteSpaceWithhComments() { // want "block should not start with a whitespace" - // This comment should be kept - - a := 1 - if a < 2 { // want "block should not start with a whitespace" - - // This comment should be kept - a = 2 - // This comment should be kept - - } // want "block should not end with a whitespace" - - if a > 2 { // want "block should not start with a whitespace" - // This comment should be kept - - a = 2 - - // This comment should be kept - } // want "block should not end with a whitespace" - - if a > 3 { // want "block should not start with a whitespace" - - // This comment should be kept - - a = 2 - - // This comment should be kept - - } // want "block should not end with a whitespace" - - _ = a - - // This comment should be kept as well -} // want "block should not end with a whitespace" - -func MultipleCommentsAreSorted() { - switch 1 { - case 1: // want "block should not start with a whitespace" - // Comment - - // Comment - fmt.Println("1") - case 2: // want "block should not start with a whitespace" - // Comment - - // Comment - fmt.Println("2") - } -} - -func ExampleT() { - fmt.Println("output") - - // Output: output -} diff --git a/testdata/src/default_config/remove_whitespace.go.golden b/testdata/src/default_config/remove_whitespace.go.golden deleted file mode 100644 index f1c0994..0000000 --- a/testdata/src/default_config/remove_whitespace.go.golden +++ /dev/null @@ -1,65 +0,0 @@ -package testpkg - -import "fmt" - -func RemoveWhitespaceNoComments() { // want "block should not start with a whitespace" - a := 1 - if a < 2 { // want "block should not start with a whitespace" - a = 2 - } // want "block should not end with a whitespace" - - switch { // want "block should not start with a whitespace" - case true: - fmt.Println("true") - - default: - fmt.Println("false") - } // want "block should not end with a whitespace" - - _ = a -} // want "block should not end with a whitespace" - -// RemoveWhiteSpaceWithhComments keeps comments even when removing newlines. -func RemoveWhiteSpaceWithhComments() { // want "block should not start with a whitespace" - // This comment should be kept - a := 1 - if a < 2 { // want "block should not start with a whitespace" - // This comment should be kept - a = 2 - // This comment should be kept - } // want "block should not end with a whitespace" - - if a > 2 { // want "block should not start with a whitespace" - // This comment should be kept - a = 2 - // This comment should be kept - } // want "block should not end with a whitespace" - - if a > 3 { // want "block should not start with a whitespace" - // This comment should be kept - a = 2 - // This comment should be kept - } // want "block should not end with a whitespace" - - _ = a - // This comment should be kept as well -} // want "block should not end with a whitespace" - -func MultipleCommentsAreSorted() { - switch 1 { - case 1: // want "block should not start with a whitespace" - // Comment - // Comment - fmt.Println("1") - case 2: // want "block should not start with a whitespace" - // Comment - // Comment - fmt.Println("2") - } -} - -func ExampleT() { - fmt.Println("output") - - // Output: output -} diff --git a/testdata/src/wip/wip.go b/testdata/src/wip/wip.go deleted file mode 100644 index a21e3cf..0000000 --- a/testdata/src/wip/wip.go +++ /dev/null @@ -1 +0,0 @@ -package testpkg diff --git a/testdata/src/wip/wip.go.golden b/testdata/src/wip/wip.go.golden deleted file mode 100644 index a21e3cf..0000000 --- a/testdata/src/wip/wip.go.golden +++ /dev/null @@ -1 +0,0 @@ -package testpkg diff --git a/testdata/src/with_config/assign_and_anything/code.go b/testdata/src/with_config/assign_and_anything/code.go deleted file mode 100644 index 344f838..0000000 --- a/testdata/src/with_config/assign_and_anything/code.go +++ /dev/null @@ -1,13 +0,0 @@ -package pkg - -import "fmt" - -func Fn() { - if x == 1 { - x = 0 - } - z := x + 2 - - fmt.Println("x") - y := "x" -} diff --git a/testdata/src/with_config/assign_and_anything/code.go.golden b/testdata/src/with_config/assign_and_anything/code.go.golden deleted file mode 100644 index 64fe3f0..0000000 --- a/testdata/src/with_config/assign_and_anything/code.go.golden +++ /dev/null @@ -1,14 +0,0 @@ -package pkg - -import "fmt" - -func Fn() { - if x == 1 { - x = 0 - } - z := x + 2 - - fmt.Println("x") - y := "x" -} - diff --git a/testdata/src/with_config/assign_and_call/code.go b/testdata/src/with_config/assign_and_call/code.go deleted file mode 100644 index d34737a..0000000 --- a/testdata/src/with_config/assign_and_call/code.go +++ /dev/null @@ -1,8 +0,0 @@ -package pkg - -func AllowAssignAndCallCuddle() { - p.token(':') - d.StartBit = p.uint() // want "assignments should only be cuddled with other assignments" - p.token('|') // want "only cuddled expressions if assigning variable or using from line above" - d.Size = p.uint() // want "assignments should only be cuddled with other assignments" -} diff --git a/testdata/src/with_config/assign_and_call/code.go.golden b/testdata/src/with_config/assign_and_call/code.go.golden deleted file mode 100644 index b245cab..0000000 --- a/testdata/src/with_config/assign_and_call/code.go.golden +++ /dev/null @@ -1,11 +0,0 @@ -package pkg - -func AllowAssignAndCallCuddle() { - p.token(':') - - d.StartBit = p.uint() // want "assignments should only be cuddled with other assignments" - - p.token('|') // want "only cuddled expressions if assigning variable or using from line above" - - d.Size = p.uint() // want "assignments should only be cuddled with other assignments" -} diff --git a/testdata/src/with_config/case_blocks/code.go b/testdata/src/with_config/case_blocks/code.go deleted file mode 100644 index 347c88a..0000000 --- a/testdata/src/with_config/case_blocks/code.go +++ /dev/null @@ -1,48 +0,0 @@ -package testpkg - -import "fmt" - -func Fn() { - switch 1 { - case 1: // want "block should not start with a whitespace" - - // Comment - - // Comment - - fmt.Println("a") - - // Comment - fmt.Println("a") // want "case block should end with newline at this size" - case 2: // want "block should not start with a whitespace" - - fmt.Println("a") - case 3: - fmt.Println("a") - case 4: - fmt.Println("a") - fmt.Println("a") - fmt.Println("a") - fmt.Println("a") // want "case block should end with newline at this size" - case 5: - fmt.Println("a") - fmt.Println("a") - fmt.Println("a") - fmt.Println("a") - // Error // want "case block should end with newline at this size" - case 6: - // Comment - // Comment - case 7: - fmt.Println("a") - /* - - Comment - - */ - - // Error // want "case block should end with newline at this size" - case 7: - fmt.Println("a") - } -} diff --git a/testdata/src/with_config/case_blocks/code.go.golden b/testdata/src/with_config/case_blocks/code.go.golden deleted file mode 100644 index f345c75..0000000 --- a/testdata/src/with_config/case_blocks/code.go.golden +++ /dev/null @@ -1,48 +0,0 @@ -package testpkg - -import "fmt" - -func Fn() { - switch 1 { - case 1: // want "block should not start with a whitespace" - // Comment - // Comment - fmt.Println("a") - - // Comment - fmt.Println("a") // want "case block should end with newline at this size" - - case 2: // want "block should not start with a whitespace" - fmt.Println("a") - case 3: - fmt.Println("a") - case 4: - fmt.Println("a") - fmt.Println("a") - fmt.Println("a") - fmt.Println("a") // want "case block should end with newline at this size" - - case 5: - fmt.Println("a") - fmt.Println("a") - fmt.Println("a") - fmt.Println("a") - // Error // want "case block should end with newline at this size" - - case 6: - // Comment - // Comment - case 7: - fmt.Println("a") - /* - - Comment - - */ - - // Error // want "case block should end with newline at this size" - - case 7: - fmt.Println("a") - } -} diff --git a/testdata/src/with_config/decl/code.go b/testdata/src/with_config/decl/code.go deleted file mode 100644 index 0837e4e..0000000 --- a/testdata/src/with_config/decl/code.go +++ /dev/null @@ -1,7 +0,0 @@ -package pkg - -func Fn() { - var a = 1 - var b = 2 - var c = 3 -} diff --git a/testdata/src/with_config/decl/code.go.golden b/testdata/src/with_config/decl/code.go.golden deleted file mode 100644 index 70e8eb5..0000000 --- a/testdata/src/with_config/decl/code.go.golden +++ /dev/null @@ -1,8 +0,0 @@ -package pkg - -func Fn() { - var a = 1 - var b = 2 - var c = 3 -} - diff --git a/testdata/src/with_config/error_check/code.go b/testdata/src/with_config/error_check/code.go deleted file mode 100644 index 67ac6b4..0000000 --- a/testdata/src/with_config/error_check/code.go +++ /dev/null @@ -1,9 +0,0 @@ -package pkg - -func Fn() { - err := ErrorProducingFunc() - - if err != nil { // want "if statements that check an error must be cuddled with the statement that assigned the error" - return err - } -} diff --git a/testdata/src/with_config/error_check/code.go.golden b/testdata/src/with_config/error_check/code.go.golden deleted file mode 100644 index 15fce78..0000000 --- a/testdata/src/with_config/error_check/code.go.golden +++ /dev/null @@ -1,8 +0,0 @@ -package pkg - -func Fn() { - err := ErrorProducingFunc() - if err != nil { // want "if statements that check an error must be cuddled with the statement that assigned the error" - return err - } -} diff --git a/testdata/src/with_config/multi_line_assign/code.go b/testdata/src/with_config/multi_line_assign/code.go deleted file mode 100644 index 5f8a372..0000000 --- a/testdata/src/with_config/multi_line_assign/code.go +++ /dev/null @@ -1,13 +0,0 @@ -package pkg - -import "fmt" - -func AllowMultiLineAssignCuddle() { - x := SomeFunc( - "taking multiple values", - "suddendly not a single line", - ) - if x != nil { // want "if statements should only be cuddled with assignments" - fmt.Println("denied") - } -} diff --git a/testdata/src/with_config/multi_line_assign/code.go.golden b/testdata/src/with_config/multi_line_assign/code.go.golden deleted file mode 100644 index 044d0aa..0000000 --- a/testdata/src/with_config/multi_line_assign/code.go.golden +++ /dev/null @@ -1,14 +0,0 @@ -package pkg - -import "fmt" - -func AllowMultiLineAssignCuddle() { - x := SomeFunc( - "taking multiple values", - "suddendly not a single line", - ) - - if x != nil { // want "if statements should only be cuddled with assignments" - fmt.Println("denied") - } -} diff --git a/testdata/src/with_config/separate_leading_whitespace/code.go b/testdata/src/with_config/separate_leading_whitespace/code.go deleted file mode 100644 index 277b797..0000000 --- a/testdata/src/with_config/separate_leading_whitespace/code.go +++ /dev/null @@ -1,18 +0,0 @@ -package pkg - -import "fmt" - -func Fn() { - // Comment one - - // Comment two - fmt.Println("a") -} - -func Fn2() { // want "block should not start with a whitespace" - // Comment one - - // Comment two - - fmt.Println("a") -} diff --git a/testdata/src/with_config/separate_leading_whitespace/code.go.golden b/testdata/src/with_config/separate_leading_whitespace/code.go.golden deleted file mode 100644 index dd6bda4..0000000 --- a/testdata/src/with_config/separate_leading_whitespace/code.go.golden +++ /dev/null @@ -1,17 +0,0 @@ -package pkg - -import "fmt" - -func Fn() { - // Comment one - - // Comment two - fmt.Println("a") -} - -func Fn2() { // want "block should not start with a whitespace" - // Comment one - - // Comment two - fmt.Println("a") -} diff --git a/testdata/src/with_config/short_decl/code.go b/testdata/src/with_config/short_decl/code.go deleted file mode 100644 index 142a972..0000000 --- a/testdata/src/with_config/short_decl/code.go +++ /dev/null @@ -1,19 +0,0 @@ -package pkg - -func Fn() { - a = "a" - b = "b" - c = "c" - - a := "a" - b := "b" - c := "c" - - a := "a" // want "short declaration should cuddle only with other short declarations" - b = "b" - c := "c" // want "short declaration should cuddle only with other short declarations" - - a := "a" // want "short declaration should cuddle only with other short declarations" - b = "b" // Hey - c := "c" // want "short declaration should cuddle only with other short declarations" -} diff --git a/testdata/src/with_config/short_decl/code.go.golden b/testdata/src/with_config/short_decl/code.go.golden deleted file mode 100644 index 835051d..0000000 --- a/testdata/src/with_config/short_decl/code.go.golden +++ /dev/null @@ -1,23 +0,0 @@ -package pkg - -func Fn() { - a = "a" - b = "b" - c = "c" - - a := "a" - b := "b" - c := "c" - - a := "a" // want "short declaration should cuddle only with other short declarations" - - b = "b" - - c := "c" // want "short declaration should cuddle only with other short declarations" - - a := "a" // want "short declaration should cuddle only with other short declarations" - - b = "b" // Hey - - c := "c" // want "short declaration should cuddle only with other short declarations" -} diff --git a/testdata/src/with_config/strict_append/code.go b/testdata/src/with_config/strict_append/code.go deleted file mode 100644 index 1a64a57..0000000 --- a/testdata/src/with_config/strict_append/code.go +++ /dev/null @@ -1,9 +0,0 @@ -package pkg - -func Fn() { - x := []string{} - y := "not going in x" - x = append(x, "not y") - z := "string" - x = append(x, "i don't care") -} diff --git a/testdata/src/with_config/strict_append/code.go.golden b/testdata/src/with_config/strict_append/code.go.golden deleted file mode 100644 index 961a4a3..0000000 --- a/testdata/src/with_config/strict_append/code.go.golden +++ /dev/null @@ -1,10 +0,0 @@ -package pkg - -func Fn() { - x := []string{} - y := "not going in x" - x = append(x, "not y") - z := "string" - x = append(x, "i don't care") -} - diff --git a/testdata/src/with_config/trailing_comments/code.go b/testdata/src/with_config/trailing_comments/code.go deleted file mode 100644 index 448d668..0000000 --- a/testdata/src/with_config/trailing_comments/code.go +++ /dev/null @@ -1,17 +0,0 @@ -package pkg - -import "fmt" - -func Fn() { - if true { - fmt.Println("a") - // Comment - } - - if true { - fmt.Println("a") - // Comment - - // Comment - } // want "block should not end with a whitespace" -} diff --git a/testdata/src/with_config/trailing_comments/code.go.golden b/testdata/src/with_config/trailing_comments/code.go.golden deleted file mode 100644 index 3f60deb..0000000 --- a/testdata/src/with_config/trailing_comments/code.go.golden +++ /dev/null @@ -1,16 +0,0 @@ -package pkg - -import "fmt" - -func Fn() { - if true { - fmt.Println("a") - // Comment - } - - if true { - fmt.Println("a") - // Comment - // Comment - } // want "block should not end with a whitespace" -} diff --git a/wsl.go b/wsl.go index 6e4bf8a..60d44b8 100644 --- a/wsl.go +++ b/wsl.go @@ -1,1411 +1,114 @@ package wsl import ( - "fmt" "go/ast" "go/token" - "reflect" - "sort" - "strings" ) -// Error reason strings. const ( - reasonAnonSwitchCuddled = "anonymous switch statements should never be cuddled" - reasonAppendCuddledWithoutUse = "append only allowed to cuddle with appended value" - reasonAssignsCuddleAssign = "assignments should only be cuddled with other assignments" - reasonBlockEndsWithWS = "block should not end with a whitespace (or comment)" - reasonBlockStartsWithWS = "block should not start with a whitespace" - reasonCaseBlockTooCuddly = "case block should end with newline at this size" - reasonDeferCuddledWithOtherVar = "defer statements should only be cuddled with expressions on same variable" - reasonExprCuddlingNonAssignedVar = "only cuddled expressions if assigning variable or using from line above" - reasonExpressionCuddledWithBlock = "expressions should not be cuddled with blocks" - reasonExpressionCuddledWithDeclOrRet = "expressions should not be cuddled with declarations or returns" - reasonForCuddledAssignWithoutUse = "for statements should only be cuddled with assignments used in the iteration" - reasonForWithoutCondition = "for statement without condition should never be cuddled" - reasonGoFuncWithoutAssign = "go statements can only invoke functions assigned on line above" - reasonMultiLineBranchCuddle = "branch statements should not be cuddled if block has more than two lines" - reasonMustCuddleErrCheck = "if statements that check an error must be cuddled with the statement that assigned the error" - reasonNeverCuddleDeclare = "declarations should never be cuddled" - reasonOnlyCuddle2LineReturn = "return statements should not be cuddled if block has more than two lines" - reasonOnlyCuddleIfWithAssign = "if statements should only be cuddled with assignments" - reasonOnlyCuddleWithUsedAssign = "if statements should only be cuddled with assignments used in the if statement itself" - reasonOnlyOneCuddleBeforeDefer = "only one cuddle assignment allowed before defer statement" - reasonOnlyOneCuddleBeforeFor = "only one cuddle assignment allowed before for statement" - reasonOnlyOneCuddleBeforeGo = "only one cuddle assignment allowed before go statement" - reasonOnlyOneCuddleBeforeIf = "only one cuddle assignment allowed before if statement" - reasonOnlyOneCuddleBeforeRange = "only one cuddle assignment allowed before range statement" - reasonOnlyOneCuddleBeforeSwitch = "only one cuddle assignment allowed before switch statement" - reasonOnlyOneCuddleBeforeTypeSwitch = "only one cuddle assignment allowed before type switch statement" - reasonRangeCuddledWithoutUse = "ranges should only be cuddled with assignments used in the iteration" - reasonShortDeclNotExclusive = "short declaration should cuddle only with other short declarations" - reasonSwitchCuddledWithoutUse = "switch statements should only be cuddled with variables switched" - reasonTypeSwitchCuddledWithoutUse = "type switch statements should only be cuddled with variables switched" + MessageAddWhitespace = "missing whitespace decreases readability" + MessageRemoveWhitespace = "unnecessary whitespace decreases readability" ) -// Warning strings. -const ( - warnTypeNotImplement = "type not implemented" - warnStmtNotImplemented = "stmt type not implemented" - warnBodyStmtTypeNotImplemented = "body statement type not implemented " - warnWSNodeTypeNotImplemented = "whitespace node type not implemented " - warnUnknownLHS = "UNKNOWN LHS" - warnUnknownRHS = "UNKNOWN RHS" -) - -// Configuration represents configurable settingds for the linter. -type Configuration struct { - // StrictAppend will do strict checking when assigning from append (x = - // append(x, y)). If this is set to true the append call must append either - // a variable assigned, called or used on the line above. Example on not - // allowed when this is true: - // - // x := []string{} - // y := "not going in X" - // x = append(x, "not y") // This is not allowed with StrictAppend - // z := "going in X" - // - // x = append(x, z) // This is allowed with StrictAppend - // - // m := transform(z) - // x = append(x, z) // So is this because Z is used above. - StrictAppend bool - - // AllowAssignAndCallCuddle allows assignments to be cuddled with variables - // used in calls on line above and calls to be cuddled with assignments of - // variables used in call on line above. - // Example supported with this set to true: - // - // x.Call() - // x = Assign() - // x.AnotherCall() - // x = AnotherAssign() - AllowAssignAndCallCuddle bool - - // AllowAssignAndAnythingCuddle allows assignments to be cuddled with anything. - // Example supported with this set to true: - // if x == 1 { - // x = 0 - // } - // z := x + 2 - // fmt.Println("x") - // y := "x" - AllowAssignAndAnythingCuddle bool - - // AllowMultiLineAssignCuddle allows cuddling to assignments even if they - // span over multiple lines. This defaults to true which allows the - // following example: - // - // err := function( - // "multiple", "lines", - // ) - // if err != nil { - // // ... - // } - AllowMultiLineAssignCuddle bool - - // If the number of lines in a case block is equal to or lager than this - // number, the case *must* end white a newline. - ForceCaseTrailingWhitespaceLimit int - - // AllowTrailingComment will allow blocks to end with comments. - AllowTrailingComment bool - - // AllowSeparatedLeadingComment will allow multiple comments in the - // beginning of a block separated with newline. Example: - // func () { - // // Comment one - // - // // Comment two - // fmt.Println("x") - // } - AllowSeparatedLeadingComment bool - - // AllowCuddleDeclaration will allow multiple var/declaration statements to - // be cuddled. This defaults to false but setting it to true will enable the - // following example: - // var foo bool - // var err error - AllowCuddleDeclaration bool - - // AllowCuddleWithCalls is a list of call idents that everything can be - // cuddled with. Defaults to calls looking like locks to support a flow like - // this: - // - // mu.Lock() - // allow := thisAssignment - AllowCuddleWithCalls []string +type Configuration struct{} - // AllowCuddleWithRHS is a list of right hand side variables that is allowed - // to be cuddled with anything. Defaults to assignments or calls looking - // like unlocks to support a flow like this: - // - // allow := thisAssignment() - // mu.Unlock() - AllowCuddleWithRHS []string - - // ForceCuddleErrCheckAndAssign will cause an error when an If statement that - // checks an error variable doesn't cuddle with the assignment of that variable. - // This defaults to false but setting it to true will cause the following - // to generate an error: - // - // err := ProduceError() - // - // if err != nil { - // return err - // } - ForceCuddleErrCheckAndAssign bool - - // When ForceCuddleErrCheckAndAssign is enabled this is a list of names - // used for error variables to check for in the conditional. - // Defaults to just "err" - ErrorVariableNames []string - - // ForceExclusiveShortDeclarations will cause an error if a short declaration - // (:=) cuddles with anything other than another short declaration. For example - // - // a := 2 - // b := 3 - // - // is allowed, but - // - // a := 2 - // b = 3 - // - // is not allowed. This logic overrides ForceCuddleErrCheckAndAssign among others. - ForceExclusiveShortDeclarations bool +type FixRange struct { + FixRangeStart token.Pos + FixRangeEnd token.Pos } -// fix is a range to fixup. -type fix struct { - fixRangeStart token.Pos - fixRangeEnd token.Pos +type Issue struct { + Message string + FixRanges []FixRange // TODO: Do we need multiple? } -// result represents the result of one error. -type result struct { - fixRanges []fix - reason string +type WSL struct { + File *ast.File + Fset *token.FileSet + Comments ast.CommentMap + Issues map[token.Pos]Issue // TODO: When do we have multiple reports? } -// processor is the type that keeps track of the file and fileset and holds the -// results from parsing the AST. -type processor struct { - config *Configuration - file *ast.File - fileSet *token.FileSet - result map[token.Pos]result - warnings []string -} - -// newProcessorWithConfig will create a Processor with the passed configuration. -func newProcessorWithConfig(file *ast.File, fileSet *token.FileSet, cfg *Configuration) *processor { - return &processor{ - config: cfg, - file: file, - fileSet: fileSet, - result: make(map[token.Pos]result), +func New(file *ast.File, fset *token.FileSet, _ *Configuration) *WSL { + return &WSL{ + Fset: fset, + File: file, + Issues: make(map[token.Pos]Issue), } } -// parseAST will parse the AST attached to the Processor instance. -func (p *processor) parseAST() { - for _, d := range p.file.Decls { - switch v := d.(type) { - case *ast.FuncDecl: - p.parseBlockBody(v.Name, v.Body) - case *ast.GenDecl: - // `go fmt` will handle proper spacing for GenDecl such as imports, - // constants etc. - default: - p.addWarning(warnTypeNotImplement, d.Pos(), v) +func (w *WSL) Run() { + for _, decl := range w.File.Decls { + if funcDecl, ok := decl.(*ast.FuncDecl); ok { + w.CheckFuncDecl(funcDecl) } } } -// parseBlockBody will parse any kind of block statements such as switch cases -// and if statements. A list of Result is returned. -func (p *processor) parseBlockBody(ident *ast.Ident, block *ast.BlockStmt) { - // Nothing to do if there's no value. - if reflect.ValueOf(block).IsNil() { +func (w *WSL) CheckFuncDecl(funcDecl *ast.FuncDecl) { + if funcDecl.Body == nil { return } - // Start by finding leading and trailing whitespaces. - p.findLeadingAndTrailingWhitespaces(ident, block, nil) - - // Parse the block body contents. - p.parseBlockStatements(block.List) + w.CheckUnnecessaryBlockLeadingNewline(funcDecl.Body) } -// parseBlockStatements will parse all the statements found in the body of a -// node. A list of Result is returned. -func (p *processor) parseBlockStatements(statements []ast.Stmt) { - for i, stmt := range statements { - // Start by checking if this statement is another block (other than if, - // for and range). This could be assignment to a function, defer or go - // call with an inline function or similar. If this is found we start by - // parsing this body block before moving on. - for _, stmtBlocks := range p.findBlockStmt(stmt) { - p.parseBlockBody(nil, stmtBlocks) - } - - firstBodyStatement := p.firstBodyStatement(i, statements) - - // First statement, nothing to do. - if i == 0 { - continue - } - - previousStatement := statements[i-1] - previousStatementIsMultiline := p.nodeStart(previousStatement) != p.nodeEnd(previousStatement) - cuddledWithLastStmt := p.nodeEnd(previousStatement) == p.nodeStart(stmt)-1 - - // If we're not cuddled and we don't need to enforce err-check cuddling - // then we can bail out here - if !cuddledWithLastStmt && !p.config.ForceCuddleErrCheckAndAssign { - continue - } - - // We don't force error cuddling for multilines. (#86) - if p.config.ForceCuddleErrCheckAndAssign && previousStatementIsMultiline && !cuddledWithLastStmt { - continue - } - - // Extract assigned variables on the line above - // which is the only thing we allow cuddling with. If the assignment is - // made over multiple lines we should not allow cuddling. - var assignedOnLineAbove []string - - // We want to keep track of what was called on the line above to support - // special handling of things such as mutexes. - var calledOnLineAbove []string - - // Check if the previous statement spans over multiple lines. - cuddledWithMultiLineAssignment := cuddledWithLastStmt && p.nodeStart(previousStatement) != p.nodeStart(stmt)-1 - - // Ensure previous line is not a multi line assignment and if not get - // rightAndLeftHandSide assigned variables. - if !cuddledWithMultiLineAssignment { - assignedOnLineAbove = p.findLHS(previousStatement) - calledOnLineAbove = p.findRHS(previousStatement) - } - - // If previous assignment is multi line and we allow it, fetch - // assignments (but only assignments). - if cuddledWithMultiLineAssignment && p.config.AllowMultiLineAssignCuddle { - if _, ok := previousStatement.(*ast.AssignStmt); ok { - assignedOnLineAbove = p.findLHS(previousStatement) - } - } - - // We could potentially have a block which require us to check the first - // argument before ruling out an allowed cuddle. - var calledOrAssignedFirstInBlock []string - - if firstBodyStatement != nil { - calledOrAssignedFirstInBlock = append(p.findLHS(firstBodyStatement), p.findRHS(firstBodyStatement)...) - } - - var ( - leftHandSide = p.findLHS(stmt) - rightHandSide = p.findRHS(stmt) - rightAndLeftHandSide = append(leftHandSide, rightHandSide...) - calledOrAssignedOnLineAbove = append(calledOnLineAbove, assignedOnLineAbove...) - ) - - // If we called some kind of lock on the line above we allow cuddling - // anything. - if atLeastOneInListsMatch(calledOnLineAbove, p.config.AllowCuddleWithCalls) { - continue - } - - // If we call some kind of unlock on this line we allow cuddling with - // anything. - if atLeastOneInListsMatch(rightHandSide, p.config.AllowCuddleWithRHS) { - continue - } - - nStatementsBefore := func(n int) bool { - if i < n { - return false - } - - for j := 1; j < n; j++ { - s1 := statements[i-j] - s2 := statements[i-(j+1)] - - if p.nodeStart(s1)-1 != p.nodeEnd(s2) { - return false - } - } - - return true - } - - nStatementsAfter := func(n int) bool { - if len(statements)-1 < i+n { - return false - } - - for j := 0; j < n; j++ { - s1 := statements[i+j] - s2 := statements[i+j+1] - - if p.nodeEnd(s1)+1 != p.nodeStart(s2) { - return false - } - } - - return true - } - - isLastStatementInBlockOfOnlyTwoLines := func() bool { - // If we're the last statement, check if there's no more than two - // lines from the starting statement and the end of this statement. - // This is to support short return functions such as: - // func (t *Typ) X() { - // t.X = true - // return t - // } - if len(statements) == 2 && i == 1 { - if p.nodeEnd(stmt)-p.nodeStart(previousStatement) <= 2 { - return true - } - } - - return false - } - - // If it's a short declaration we should not cuddle with anything else - // if ForceExclusiveShortDeclarations is set on; either this or the - // previous statement could be the short decl, so we'll find out which - // it was and use *that* statement's position - if p.config.ForceExclusiveShortDeclarations && cuddledWithLastStmt { - if p.isShortDecl(stmt) && !p.isShortDecl(previousStatement) { - var reportNode ast.Node = previousStatement - - cm := ast.NewCommentMap(p.fileSet, stmt, p.file.Comments) - if cg, ok := cm[stmt]; ok && len(cg) > 0 { - for _, c := range cg { - if c.Pos() > previousStatement.End() && c.End() < stmt.Pos() { - reportNode = c - } - } - } - - p.addErrorRange( - stmt.Pos(), - reportNode.End(), - reportNode.End(), - reasonShortDeclNotExclusive, - ) - } else if p.isShortDecl(previousStatement) && !p.isShortDecl(stmt) { - p.addErrorRange( - previousStatement.Pos(), - stmt.Pos(), - stmt.Pos(), - reasonShortDeclNotExclusive, - ) - } - } - - // If it's not an if statement and we're not cuddled move on. The only - // reason we need to keep going for if statements is to check if we - // should be cuddled with an error check. - if _, ok := stmt.(*ast.IfStmt); !ok { - if !cuddledWithLastStmt { - continue - } - } - - reportNewlineTwoLinesAbove := func(n1, n2 ast.Node, reason string) { - if atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) || - atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - // If both the assignment on the line above _and_ the assignment - // two lines above is part of line or first in block, add the - // newline as if non were. - _, isAssignmentTwoLinesAbove := statements[i-2].(*ast.AssignStmt) - assignedTwoLinesAbove := p.findLHS(statements[i-2]) - - if isAssignmentTwoLinesAbove && - (atLeastOneInListsMatch(rightAndLeftHandSide, assignedTwoLinesAbove) || - atLeastOneInListsMatch(assignedTwoLinesAbove, calledOrAssignedFirstInBlock)) { - p.addWhitespaceBeforeError(n1, reason) - } else { - // If the variable on the line above is allowed to be - // cuddled, break two lines above so we keep the proper - // cuddling. - p.addErrorRange(n1.Pos(), n2.Pos(), n2.Pos(), reason) - } - } else { - // If not, break here so we separate the cuddled variable. - p.addWhitespaceBeforeError(n1, reason) - } - } - - switch t := stmt.(type) { - case *ast.IfStmt: - checkingErrInitializedInline := func() bool { - if t.Init == nil { - return false - } - - // Variables were initialized inline in the if statement - // Let's make sure it's the err just to be safe - return atLeastOneInListsMatch(p.findLHS(t.Init), p.config.ErrorVariableNames) - } - - if !cuddledWithLastStmt { - checkingErr := atLeastOneInListsMatch(rightAndLeftHandSide, p.config.ErrorVariableNames) - if checkingErr { - // We only want to enforce cuddling error checks if the - // error was assigned on the line above. See - // https://github.com/bombsimon/wsl/issues/78. - // This is needed since `assignedOnLineAbove` is not - // actually just assignments but everything from LHS in the - // previous statement. This means that if previous line was - // `if err ...`, `err` will now be in the list - // `assignedOnLineAbove`. - if _, ok := previousStatement.(*ast.AssignStmt); !ok { - continue - } - - if checkingErrInitializedInline() { - continue - } - - if atLeastOneInListsMatch(assignedOnLineAbove, p.config.ErrorVariableNames) { - p.addErrorRange( - stmt.Pos(), - previousStatement.End(), - stmt.Pos(), - reasonMustCuddleErrCheck, - ) - } - } - - continue - } - - if len(assignedOnLineAbove) == 0 { - p.addWhitespaceBeforeError(t, reasonOnlyCuddleIfWithAssign) - continue - } - - if nStatementsBefore(2) { - reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeIf) - continue - } - - if atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - continue - } - - if atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - continue - } - - p.addWhitespaceBeforeError(t, reasonOnlyCuddleWithUsedAssign) - case *ast.ReturnStmt: - if isLastStatementInBlockOfOnlyTwoLines() { - continue - } - - p.addWhitespaceBeforeError(t, reasonOnlyCuddle2LineReturn) - case *ast.BranchStmt: - if isLastStatementInBlockOfOnlyTwoLines() { - continue - } - - p.addWhitespaceBeforeError(t, reasonMultiLineBranchCuddle) - case *ast.AssignStmt: - // append is usually an assignment but should not be allowed to be - // cuddled with anything not appended. - if len(rightHandSide) > 0 && rightHandSide[len(rightHandSide)-1] == "append" { - if p.config.StrictAppend { - if !atLeastOneInListsMatch(calledOrAssignedOnLineAbove, rightHandSide) { - p.addWhitespaceBeforeError(t, reasonAppendCuddledWithoutUse) - } - } - - continue - } - - switch previousStatement.(type) { - case *ast.AssignStmt, *ast.IncDecStmt: - continue - } - - if p.config.AllowAssignAndAnythingCuddle { - continue - } - - if _, ok := previousStatement.(*ast.DeclStmt); ok && p.config.AllowCuddleDeclaration { - continue - } - - // If the assignment is from a type or variable called on the line - // above we can allow it by setting AllowAssignAndCallCuddle to - // true. - // Example (x is used): - // x.function() - // a.Field = x.anotherFunction() - if p.config.AllowAssignAndCallCuddle { - if atLeastOneInListsMatch(calledOrAssignedOnLineAbove, rightAndLeftHandSide) { - continue - } - } - - p.addWhitespaceBeforeError(t, reasonAssignsCuddleAssign) - case *ast.IncDecStmt: - switch previousStatement.(type) { - case *ast.AssignStmt, *ast.IncDecStmt: - continue - } - - p.addWhitespaceBeforeError(t, reasonAssignsCuddleAssign) - - case *ast.DeclStmt: - if !p.config.AllowCuddleDeclaration { - p.addWhitespaceBeforeError(t, reasonNeverCuddleDeclare) - } - case *ast.ExprStmt: - switch previousStatement.(type) { - case *ast.DeclStmt, *ast.ReturnStmt: - if p.config.AllowAssignAndCallCuddle && p.config.AllowCuddleDeclaration { - continue - } - - p.addWhitespaceBeforeError(t, reasonExpressionCuddledWithDeclOrRet) - case *ast.IfStmt, *ast.RangeStmt, *ast.SwitchStmt: - p.addWhitespaceBeforeError(t, reasonExpressionCuddledWithBlock) - } - - // If the expression is called on a type or variable used or - // assigned on the line we can allow it by setting - // AllowAssignAndCallCuddle to true. - // Example of allowed cuddled (x is used): - // a.Field = x.func() - // x.function() - if p.config.AllowAssignAndCallCuddle { - if atLeastOneInListsMatch(calledOrAssignedOnLineAbove, rightAndLeftHandSide) { - continue - } - } - - // If we assigned variables on the line above but didn't use them in - // this expression there should probably be a newline between them. - if len(assignedOnLineAbove) > 0 && !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - p.addWhitespaceBeforeError(t, reasonExprCuddlingNonAssignedVar) - } - case *ast.RangeStmt: - if nStatementsBefore(2) { - reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeRange) - continue - } - - if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - p.addWhitespaceBeforeError(t, reasonRangeCuddledWithoutUse) - } - } - case *ast.DeferStmt: - if _, ok := previousStatement.(*ast.DeferStmt); ok { - // We may cuddle multiple defers to group logic. - continue - } - - if nStatementsBefore(2) { - // We allow cuddling defer if the defer references something - // used two lines above. - // There are several reasons to why we do this. - // Originally there was a special use case only for "Close" - // - // https://github.com/bombsimon/wsl/issues/31 which links to - // resp, err := client.Do(req) - // if err != nil { - // return err - // } - // defer resp.Body.Close() - // - // After a discussion in a followup issue it makes sense to not - // only hard code `Close` but for anything that's referenced two - // statements above. - // - // https://github.com/bombsimon/wsl/issues/85 - // db, err := OpenDB() - // require.NoError(t, err) - // defer db.Close() - // - // All of this is only allowed if there's exactly three cuddled - // statements, otherwise the regular rules apply. - if !nStatementsBefore(3) && !nStatementsAfter(1) { - variablesTwoLinesAbove := append(p.findLHS(statements[i-2]), p.findRHS(statements[i-2])...) - if atLeastOneInListsMatch(rightHandSide, variablesTwoLinesAbove) { - continue - } - } - - reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeDefer) - - continue - } - - // Be extra nice with RHS, it's common to use this for locks: - // m.Lock() - // defer m.Unlock() - previousRHS := p.findRHS(previousStatement) - if atLeastOneInListsMatch(rightHandSide, previousRHS) { - continue - } - - // Allow use to cuddled defer func literals with usages on line - // above. Example: - // b := getB() - // defer func() { - // makesSenseToUse(b) - // }() - if c, ok := t.Call.Fun.(*ast.FuncLit); ok { - funcLitFirstStmt := append(p.findLHS(c.Body), p.findRHS(c.Body)...) - - if atLeastOneInListsMatch(assignedOnLineAbove, funcLitFirstStmt) { - continue - } - } - - if atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - continue - } - - if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - p.addWhitespaceBeforeError(t, reasonDeferCuddledWithOtherVar) - } - case *ast.ForStmt: - if len(rightAndLeftHandSide) == 0 { - p.addWhitespaceBeforeError(t, reasonForWithoutCondition) - continue - } - - if nStatementsBefore(2) { - reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeFor) - continue - } - - // The same rule applies for ranges as for if statements, see - // comments regarding variable usages on the line before or as the - // first line in the block for details. - if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - p.addWhitespaceBeforeError(t, reasonForCuddledAssignWithoutUse) - } - } - case *ast.GoStmt: - if _, ok := previousStatement.(*ast.GoStmt); ok { - continue - } - - if nStatementsBefore(2) { - reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeGo) - continue - } - - if c, ok := t.Call.Fun.(*ast.SelectorExpr); ok { - goCallArgs := append(p.findLHS(c.X), p.findRHS(c.X)...) - - if atLeastOneInListsMatch(calledOnLineAbove, goCallArgs) { - continue - } - } - - if c, ok := t.Call.Fun.(*ast.FuncLit); ok { - goCallArgs := append(p.findLHS(c.Body), p.findRHS(c.Body)...) - - if atLeastOneInListsMatch(assignedOnLineAbove, goCallArgs) { - continue - } - } - - if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - p.addWhitespaceBeforeError(t, reasonGoFuncWithoutAssign) - } - case *ast.SwitchStmt: - if nStatementsBefore(2) { - reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeSwitch) - continue - } - - if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) { - if len(rightAndLeftHandSide) == 0 { - p.addWhitespaceBeforeError(t, reasonAnonSwitchCuddled) - } else { - p.addWhitespaceBeforeError(t, reasonSwitchCuddledWithoutUse) - } - } - case *ast.TypeSwitchStmt: - if nStatementsBefore(2) { - reportNewlineTwoLinesAbove(t, statements[i-1], reasonOnlyOneCuddleBeforeTypeSwitch) - continue - } - - // Allowed to type assert on variable assigned on line above. - if !atLeastOneInListsMatch(rightHandSide, assignedOnLineAbove) { - // Allow type assertion on variables used in the first case - // immediately. - if !atLeastOneInListsMatch(assignedOnLineAbove, calledOrAssignedFirstInBlock) { - p.addWhitespaceBeforeError(t, reasonTypeSwitchCuddledWithoutUse) - } - } - case *ast.CaseClause, *ast.CommClause: - // Case clauses will be checked by not allowing leading ot trailing - // whitespaces within the block. There's nothing in the case itself - // that may be cuddled. - default: - p.addWarning(warnStmtNotImplemented, t.Pos(), t) - } - } -} - -// firstBodyStatement returns the first statement inside a body block. This is -// because variables may be cuddled with conditions or statements if it's used -// directly as the first argument inside a body. -// The body will then be parsed as a *ast.BlockStmt (regular block) or as a list -// of []ast.Stmt (case block). -func (p *processor) firstBodyStatement(i int, allStmt []ast.Stmt) ast.Node { - stmt := allStmt[i] - - // Start by checking if the statement has a body (probably if-statement, - // a range, switch case or similar. Whenever a body is found we start by - // parsing it before moving on in the AST. - statementBody := reflect.Indirect(reflect.ValueOf(stmt)).FieldByName("Body") - - // Some cases allow cuddling depending on the first statement in a body - // of a block or case. If possible extract the first statement. - var firstBodyStatement ast.Node - - if !statementBody.IsValid() { - return firstBodyStatement - } - - switch statementBodyContent := statementBody.Interface().(type) { - case *ast.BlockStmt: - if len(statementBodyContent.List) > 0 { - firstBodyStatement = statementBodyContent.List[0] - - // If the first body statement is a *ast.CaseClause we're - // actually interested in the **next** body to know what's - // inside the first case. - if x, ok := firstBodyStatement.(*ast.CaseClause); ok { - if len(x.Body) > 0 { - firstBodyStatement = x.Body[0] - } - } - } - - // If statement bodies will be parsed already when finding block bodies. - // The reason is because if/else-if/else chains is nested in the AST - // where the else bit is a part of the if statement. Since if statements - // is the only statement that can be chained like this we exclude it - // from parsing it again here. - if _, ok := stmt.(*ast.IfStmt); !ok { - p.parseBlockBody(nil, statementBodyContent) - } - case []ast.Stmt: - // The Body field for an *ast.CaseClause or *ast.CommClause is of type - // []ast.Stmt. We must check leading and trailing whitespaces and then - // pass the statements to parseBlockStatements to parse it's content. - var nextStatement ast.Node - - // Check if there's more statements (potential cases) after the - // current one. - if len(allStmt)-1 > i { - nextStatement = allStmt[i+1] - } - - p.findLeadingAndTrailingWhitespaces(nil, stmt, nextStatement) - p.parseBlockStatements(statementBodyContent) - default: - p.addWarning( - warnBodyStmtTypeNotImplemented, - stmt.Pos(), statementBodyContent, - ) - } - - return firstBodyStatement -} - -func (p *processor) findLHS(node ast.Node) []string { - var lhs []string - - if node == nil { - return lhs - } - - switch t := node.(type) { - case *ast.BasicLit, *ast.FuncLit, *ast.SelectStmt, - *ast.LabeledStmt, *ast.ForStmt, *ast.SwitchStmt, - *ast.ReturnStmt, *ast.GoStmt, *ast.CaseClause, - *ast.CommClause, *ast.CallExpr, *ast.UnaryExpr, - *ast.BranchStmt, *ast.TypeSpec, *ast.ChanType, - *ast.DeferStmt, *ast.TypeAssertExpr, *ast.RangeStmt: - // Nothing to add to LHS - case *ast.IncDecStmt: - return p.findLHS(t.X) - case *ast.Ident: - return []string{t.Name} - case *ast.AssignStmt: - for _, v := range t.Lhs { - lhs = append(lhs, p.findLHS(v)...) - } - case *ast.GenDecl: - for _, v := range t.Specs { - lhs = append(lhs, p.findLHS(v)...) - } - case *ast.ValueSpec: - for _, v := range t.Names { - lhs = append(lhs, p.findLHS(v)...) - } - case *ast.BlockStmt: - for _, v := range t.List { - lhs = append(lhs, p.findLHS(v)...) - } - case *ast.BinaryExpr: - return append( - p.findLHS(t.X), - p.findLHS(t.Y)..., - ) - case *ast.DeclStmt: - return p.findLHS(t.Decl) - case *ast.IfStmt: - return p.findLHS(t.Cond) - case *ast.TypeSwitchStmt: - return p.findLHS(t.Assign) - case *ast.SendStmt: - return p.findLHS(t.Chan) - default: - if x, ok := maybeX(t); ok { - return p.findLHS(x) - } - - p.addWarning(warnUnknownLHS, t.Pos(), t) - } - - return lhs -} - -func (p *processor) findRHS(node ast.Node) []string { - var rhs []string - - if node == nil { - return rhs - } - - switch t := node.(type) { - case *ast.BasicLit, *ast.SelectStmt, *ast.ChanType, - *ast.LabeledStmt, *ast.DeclStmt, *ast.BranchStmt, - *ast.TypeSpec, *ast.ArrayType, *ast.CaseClause, - *ast.CommClause, *ast.MapType, *ast.FuncLit: - // Nothing to add to RHS - case *ast.Ident: - return []string{t.Name} - case *ast.SelectorExpr: - // TODO: Should this be RHS? - // t.X is needed for defer as of now and t.Sel needed for special - // functions such as Lock() - rhs = p.findRHS(t.X) - rhs = append(rhs, p.findRHS(t.Sel)...) - case *ast.AssignStmt: - for _, v := range t.Rhs { - rhs = append(rhs, p.findRHS(v)...) - } - case *ast.CallExpr: - for _, v := range t.Args { - rhs = append(rhs, p.findRHS(v)...) - } - - rhs = append(rhs, p.findRHS(t.Fun)...) - case *ast.CompositeLit: - for _, v := range t.Elts { - rhs = append(rhs, p.findRHS(v)...) - } - case *ast.IfStmt: - rhs = append(rhs, p.findRHS(t.Cond)...) - rhs = append(rhs, p.findRHS(t.Init)...) - case *ast.BinaryExpr: - return append( - p.findRHS(t.X), - p.findRHS(t.Y)..., - ) - case *ast.TypeSwitchStmt: - return p.findRHS(t.Assign) - case *ast.ReturnStmt: - for _, v := range t.Results { - rhs = append(rhs, p.findRHS(v)...) - } - case *ast.BlockStmt: - for _, v := range t.List { - rhs = append(rhs, p.findRHS(v)...) - } - case *ast.SwitchStmt: - return p.findRHS(t.Tag) - case *ast.GoStmt: - return p.findRHS(t.Call) - case *ast.ForStmt: - return p.findRHS(t.Cond) - case *ast.DeferStmt: - return p.findRHS(t.Call) - case *ast.SendStmt: - return p.findLHS(t.Value) - case *ast.IndexExpr: - rhs = append(rhs, p.findRHS(t.Index)...) - rhs = append(rhs, p.findRHS(t.X)...) - case *ast.SliceExpr: - rhs = append(rhs, p.findRHS(t.X)...) - rhs = append(rhs, p.findRHS(t.Low)...) - rhs = append(rhs, p.findRHS(t.High)...) - case *ast.KeyValueExpr: - rhs = p.findRHS(t.Key) - rhs = append(rhs, p.findRHS(t.Value)...) - default: - if x, ok := maybeX(t); ok { - return p.findRHS(x) - } - - p.addWarning(warnUnknownRHS, t.Pos(), t) - } - - return rhs -} - -func (p *processor) isShortDecl(node ast.Node) bool { - if t, ok := node.(*ast.AssignStmt); ok { - return t.Tok == token.DEFINE - } - - return false -} - -func (p *processor) findBlockStmt(node ast.Node) []*ast.BlockStmt { - var blocks []*ast.BlockStmt - - switch t := node.(type) { - case *ast.BlockStmt: - return []*ast.BlockStmt{t} - case *ast.AssignStmt: - for _, x := range t.Rhs { - blocks = append(blocks, p.findBlockStmt(x)...) - } - case *ast.CallExpr: - blocks = append(blocks, p.findBlockStmt(t.Fun)...) - - for _, x := range t.Args { - blocks = append(blocks, p.findBlockStmt(x)...) - } - case *ast.FuncLit: - blocks = append(blocks, t.Body) - case *ast.ExprStmt: - blocks = append(blocks, p.findBlockStmt(t.X)...) - case *ast.ReturnStmt: - for _, x := range t.Results { - blocks = append(blocks, p.findBlockStmt(x)...) - } - case *ast.DeferStmt: - blocks = append(blocks, p.findBlockStmt(t.Call)...) - case *ast.GoStmt: - blocks = append(blocks, p.findBlockStmt(t.Call)...) - case *ast.IfStmt: - blocks = append([]*ast.BlockStmt{t.Body}, p.findBlockStmt(t.Else)...) - } - - return blocks -} - -// maybeX extracts the X field from an AST node and returns it with a true value -// if it exists. If the node doesn't have an X field nil and false is returned. -// Known fields with X that are handled: -// IndexExpr, ExprStmt, SelectorExpr, StarExpr, ParentExpr, TypeAssertExpr, -// RangeStmt, UnaryExpr, ParenExpr, SliceExpr, IncDecStmt. -func maybeX(node interface{}) (ast.Node, bool) { - maybeHasX := reflect.Indirect(reflect.ValueOf(node)).FieldByName("X") - if !maybeHasX.IsValid() { - return nil, false - } - - n, ok := maybeHasX.Interface().(ast.Node) - if !ok { - return nil, false - } - - return n, true -} - -func atLeastOneInListsMatch(listOne, listTwo []string) bool { - sliceToMap := func(s []string) map[string]struct{} { - m := map[string]struct{}{} - - for _, v := range s { - m[v] = struct{}{} - } - - return m - } - - m1 := sliceToMap(listOne) - m2 := sliceToMap(listTwo) - - for k1 := range m1 { - if _, ok := m2[k1]; ok { - return true - } - } - - for k2 := range m2 { - if _, ok := m1[k2]; ok { - return true - } - } - - return false -} - -// findLeadingAndTrailingWhitespaces will find leading and trailing whitespaces -// in a node. The method takes comments in consideration which will make the -// parser more gentle. -func (p *processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, nextStatement ast.Node) { - var ( - commentMap = ast.NewCommentMap(p.fileSet, stmt, p.file.Comments) - blockStatements []ast.Stmt - blockStartLine int - blockEndLine int - blockStartPos token.Pos - blockEndPos token.Pos - isCase bool - ) - - // Depending on the block type, get the statements in the block and where - // the block starts (and ends). - switch t := stmt.(type) { - case *ast.BlockStmt: - blockStatements = t.List - blockStartPos = t.Lbrace - blockEndPos = t.Rbrace - case *ast.CaseClause: - blockStatements = t.Body - blockStartPos = t.Colon - isCase = true - case *ast.CommClause: - blockStatements = t.Body - blockStartPos = t.Colon - isCase = true - default: - p.addWarning(warnWSNodeTypeNotImplemented, stmt.Pos(), stmt) - - return - } - - // Ignore empty blocks even if they have newlines or just comments. - if len(blockStatements) < 1 { +func (w *WSL) CheckUnnecessaryBlockLeadingNewline(body *ast.BlockStmt) { + // No statements in the block, let's leave it as is. + if len(body.List) == 0 { return } - blockStartLine = p.fileSet.Position(blockStartPos).Line - blockEndLine = p.fileSet.Position(blockEndPos).Line - - // No whitespace possible if LBrace and RBrace is on the same line. - if blockStartLine == blockEndLine { - return - } - - var ( - firstStatement = blockStatements[0] - lastStatement = blockStatements[len(blockStatements)-1] - ) - - // Get the comment related to the first statement, we do allow commends in - // the beginning of a block before the first statement. - var ( - openingNodePos = blockStartPos + 1 - lastLeadingComment ast.Node - ) - - var ( - firstStatementCommentGroups []*ast.CommentGroup - lastStatementCommentGroups []*ast.CommentGroup - ) - - if cg, ok := commentMap[firstStatement]; ok && !isCase { - firstStatementCommentGroups = cg - } else { - // TODO: Just like with trailing whitespaces comments in a case block is - // tied to the last token of the first statement. For now we iterate over - // all comments in the stmt and grab those that's after colon and before - // first statement. - for _, cg := range commentMap { - if len(cg) < 1 { - continue - } - - // If we have comments and the last comment ends before the first - // statement and the node is after the colon, this must be the node - // mapped to comments. - for _, c := range cg { - if c.End() < firstStatement.Pos() && c.Pos() > blockStartPos { - firstStatementCommentGroups = append(firstStatementCommentGroups, c) - } - } - - // And same if we have comments where the first comment is after the - // last statement but before the next statement (next case). As with - // the other things, if there is not next statement it's no next - // case and the logic will be handled when parsing the block. - if nextStatement == nil { - continue - } - - for _, c := range cg { - if c.Pos() > lastStatement.End() && c.End() < nextStatement.Pos() { - lastStatementCommentGroups = append(lastStatementCommentGroups, c) - } - } - } - - // Since the comments come from a map they might not be ordered meaning - // that the last and first comment groups can be in the wrong order. We - // fix this by sorting all comments by pos after adding them all to the - // slice. - sort.Slice(firstStatementCommentGroups, func(i, j int) bool { - return firstStatementCommentGroups[i].Pos() < firstStatementCommentGroups[j].Pos() - }) - - sort.Slice(lastStatementCommentGroups, func(i, j int) bool { - return lastStatementCommentGroups[i].Pos() < lastStatementCommentGroups[j].Pos() - }) - } - - for _, commentGroup := range firstStatementCommentGroups { - // If the comment group is on the same line as the block start - // (LBrace) we should not consider it. - if p.nodeEnd(commentGroup) == blockStartLine { - openingNodePos = commentGroup.End() - continue - } - - // We only care about comments before our statement from the comment - // map. As soon as we hit comments after our statement let's break - // out! - if commentGroup.Pos() > firstStatement.Pos() { - break - } - - // We never allow leading whitespace for the first comment. - if lastLeadingComment == nil && p.nodeStart(commentGroup)-1 != blockStartLine { - p.addErrorRange( - openingNodePos, - openingNodePos, - commentGroup.Pos(), - reasonBlockStartsWithWS, - ) - } - - // If lastLeadingComment is set this is not the first comment so we - // should remove whitespace between them if we don't explicitly - // allow it. - if lastLeadingComment != nil && !p.config.AllowSeparatedLeadingComment { - if p.nodeStart(commentGroup)+1 != p.nodeEnd(lastLeadingComment) { - p.addErrorRange( - openingNodePos, - lastLeadingComment.End(), - commentGroup.Pos(), - reasonBlockStartsWithWS, - ) - } - } - - lastLeadingComment = commentGroup - } - - lastNodePos := openingNodePos - if lastLeadingComment != nil { - lastNodePos = lastLeadingComment.End() - blockStartLine = p.nodeEnd(lastLeadingComment) - } - - // Check if we have a whitespace between the last node which can be the - // Lbrace, a comment on the same line or the last comment if we have - // comments inside the actual block and the first statement. This is never - // allowed. - if p.nodeStart(firstStatement)-1 != blockStartLine { - p.addErrorRange( - openingNodePos, - lastNodePos, - firstStatement.Pos(), - reasonBlockStartsWithWS, - ) - } - - // If the blockEndLine is not 0 we're a regular block (not case). - if blockEndLine != 0 { - // We don't want to reject example functions since they have to end with - // a comment. - if isExampleFunc(ident) { - return - } - - var ( - lastNode ast.Node = lastStatement - trailingComments []ast.Node - ) - - // Check if we have an comments _after_ the last statement and update - // the last node if so. - if c, ok := commentMap[lastStatement]; ok { - lastComment := c[len(c)-1] - if lastComment.Pos() > lastStatement.End() && lastComment.Pos() < stmt.End() { - lastNode = lastComment - } - } - - // TODO: This should be improved. - // The trailing comments are mapped to the last statement item which can - // be anything depending on what the last statement is. - // In `fmt.Println("hello")`, trailing comments will be mapped to - // `*ast.BasicLit` for the "hello" string. - // A short term improvement can be to cache this but for now we naively - // iterate over all items when we check a block. - for _, commentGroups := range commentMap { - for _, commentGroup := range commentGroups { - if commentGroup.Pos() < lastNode.End() || commentGroup.End() > stmt.End() { - continue + openingPos := body.Lbrace + 1 + firstStmt := body.List[0].Pos() + + comments := ast.NewCommentMap(w.Fset, body, w.File.Comments) + for _, commentGroup := range comments { + for _, comment := range commentGroup { + // The comment starts after the current opening position (originally + // the LBrace) and ends before the current first statement + // (originally first body.List item). + if comment.Pos() > openingPos && comment.End() < firstStmt { + openingPosLine := w.lineFor(openingPos) + commentStartLine := w.lineFor(comment.Pos()) + + // If comment starts at the same line as the opening position it + // should just extend the position for the fixer if needed. + // func fn() { // This comment starts at the same line as LBrace + if commentStartLine == openingPosLine { + openingPos = comment.End() + } else { + firstStmt = comment.Pos() } - - trailingComments = append(trailingComments, commentGroup) } } - - // TODO: Should this be relaxed? - // Given the old code we only allowed trailing newline if it was - // directly tied to the last statement so for backwards compatibility - // we'll do the same. This means we fail all but the last whitespace - // even when allowing trailing comments. - for _, comment := range trailingComments { - if p.nodeStart(comment)-p.nodeEnd(lastNode) > 1 { - p.addErrorRange( - blockEndPos, - lastNode.End(), - comment.Pos(), - reasonBlockEndsWithWS, - ) - } - - lastNode = comment - } - - if !p.config.AllowTrailingComment && p.nodeEnd(stmt)-1 != p.nodeEnd(lastStatement) { - p.addErrorRange( - blockEndPos, - lastNode.End(), - stmt.End()-1, - reasonBlockEndsWithWS, - ) - } - - return - } - - // Nothing to do if we're not looking for enforced newline. - if p.config.ForceCaseTrailingWhitespaceLimit == 0 { - return - } - - // If we don't have any nextStatement the trailing whitespace will be - // handled when parsing the switch. If we do have a next statement we can - // see where it starts by getting it's colon position. We set the end of the - // current case to the position of the next case. - switch nextStatement.(type) { - case *ast.CaseClause, *ast.CommClause: - default: - // No more cases - return - } - - var closingNode ast.Node = lastStatement - for _, commentGroup := range lastStatementCommentGroups { - // TODO: In future versions we might want to close the gaps between - // comments. However this is not currently reported in v3 so we - // won't add this for now. - // if p.nodeStart(commentGroup)-1 != p.nodeEnd(closingNode) {} - closingNode = commentGroup } - totalRowsInCase := p.nodeEnd(closingNode) - blockStartLine - if totalRowsInCase < p.config.ForceCaseTrailingWhitespaceLimit { - return - } + openingPosLine := w.Fset.PositionFor(openingPos, false).Line + firstStmtLine := w.Fset.PositionFor(firstStmt, false).Line - if p.nodeEnd(closingNode)+1 == p.nodeStart(nextStatement) { - p.addErrorRange( - closingNode.Pos(), - closingNode.End(), - closingNode.End(), - reasonCaseBlockTooCuddly, - ) + if openingPosLine != firstStmtLine-1 { + w.addError(openingPos, openingPos, firstStmt, MessageRemoveWhitespace) } } -func isExampleFunc(ident *ast.Ident) bool { - return ident != nil && strings.HasPrefix(ident.Name, "Example") +func (w *WSL) lineFor(pos token.Pos) int { + return w.Fset.PositionFor(pos, false).Line } -func (p *processor) nodeStart(node ast.Node) int { - return p.fileSet.Position(node.Pos()).Line -} - -func (p *processor) nodeEnd(node ast.Node) int { - line := p.fileSet.Position(node.End()).Line - - if isEmptyLabeledStmt(node) { - return p.fileSet.Position(node.Pos()).Line - } - - return line -} - -func isEmptyLabeledStmt(node ast.Node) bool { - v, ok := node.(*ast.LabeledStmt) +func (w *WSL) addError(report, start, end token.Pos, message string) { + issue, ok := w.Issues[report] if !ok { - return false - } - - _, empty := v.Stmt.(*ast.EmptyStmt) - - return empty -} - -func (p *processor) addWhitespaceBeforeError(node ast.Node, reason string) { - p.addErrorRange(node.Pos(), node.Pos(), node.Pos(), reason) -} - -func (p *processor) addErrorRange(reportAt, start, end token.Pos, reason string) { - report, ok := p.result[reportAt] - if !ok { - report = result{ - reason: reason, - fixRanges: []fix{}, + issue = Issue{ + Message: message, + FixRanges: []FixRange{}, } } - report.fixRanges = append(report.fixRanges, fix{ - fixRangeStart: start, - fixRangeEnd: end, + issue.FixRanges = append(issue.FixRanges, FixRange{ + FixRangeStart: start, + FixRangeEnd: end, }) - p.result[reportAt] = report -} - -func (p *processor) addWarning(w string, pos token.Pos, t interface{}) { - position := p.fileSet.Position(pos) - - p.warnings = append(p.warnings, - fmt.Sprintf("%s:%d: %s (%T)", position.Filename, position.Line, w, t), - ) + w.Issues[report] = issue } diff --git a/wsl_test.go b/wsl_test.go index 4847e0a..e981390 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -1,100 +1,14 @@ package wsl import ( - "path/filepath" "testing" "golang.org/x/tools/go/analysis/analysistest" ) -func TestWIP(t *testing.T) { - testdata := analysistest.TestData() - analyzer := NewAnalyzer(nil) - - analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "wip") -} - func TestDefaultConfig(t *testing.T) { testdata := analysistest.TestData() analyzer := NewAnalyzer(nil) analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "default_config") } - -func TestWithConfig(t *testing.T) { - testdata := analysistest.TestData() - - for _, tc := range []struct { - subdir string - configFn func(*Configuration) - }{ - { - subdir: "case_blocks", - configFn: func(config *Configuration) { - config.ForceCaseTrailingWhitespaceLimit = 3 - }, - }, - { - subdir: "multi_line_assign", - configFn: func(config *Configuration) { - config.AllowMultiLineAssignCuddle = false - }, - }, - { - subdir: "assign_and_call", - configFn: func(config *Configuration) { - config.AllowAssignAndCallCuddle = false - }, - }, - { - subdir: "trailing_comments", - configFn: func(config *Configuration) { - config.AllowTrailingComment = true - }, - }, - { - subdir: "separate_leading_whitespace", - configFn: func(config *Configuration) { - config.AllowSeparatedLeadingComment = true - }, - }, - { - subdir: "error_check", - configFn: func(config *Configuration) { - config.ForceCuddleErrCheckAndAssign = true - }, - }, - { - subdir: "short_decl", - configFn: func(config *Configuration) { - config.ForceExclusiveShortDeclarations = true - }, - }, - { - subdir: "strict_append", - configFn: func(config *Configuration) { - config.StrictAppend = false - }, - }, - { - subdir: "assign_and_anything", - configFn: func(config *Configuration) { - config.AllowAssignAndAnythingCuddle = true - }, - }, - { - subdir: "decl", - configFn: func(config *Configuration) { - config.AllowCuddleDeclaration = true - }, - }, - } { - t.Run(tc.subdir, func(t *testing.T) { - config := defaultConfig() - tc.configFn(config) - - analyzer := NewAnalyzer(config) - analysistest.RunWithSuggestedFixes(t, testdata, analyzer, filepath.Join("with_config", tc.subdir)) - }) - } -} From e15c58a8207f549d12ba5c63099a6ed4cd80980b Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 16 Mar 2024 22:13:34 +0100 Subject: [PATCH 002/112] Update dependencies, improve leading whitespace --- go.mod | 7 ++++--- go.sum | 8 ++++++++ .../src/default_config/leading_whitespace.go | 2 +- .../leading_whitespace.go.golden | 3 +-- wsl.go | 18 ++++++++++++++++-- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index def9fb2..36f900f 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,10 @@ module github.com/bombsimon/wsl/v4 go 1.19 -require golang.org/x/tools v0.6.0 +require golang.org/x/tools v0.19.0 require ( - golang.org/x/mod v0.8.0 // indirect - golang.org/x/sys v0.5.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + golang.org/x/mod v0.16.0 // indirect + golang.org/x/sys v0.18.0 // indirect ) diff --git a/go.sum b/go.sum index 44d387e..ebd3f89 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,15 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= diff --git a/testdata/src/default_config/leading_whitespace.go b/testdata/src/default_config/leading_whitespace.go index 97a4e62..fe9808a 100644 --- a/testdata/src/default_config/leading_whitespace.go +++ b/testdata/src/default_config/leading_whitespace.go @@ -14,7 +14,7 @@ func fn2() { // want "unnecessary whitespace decreases readability" } func fn3() { - // Space after comment - v5.0.0 BREAKING CHANGE + // Space after comment // want "unnecessary whitespace decreases readability" fmt.Println("Hello, World") } diff --git a/testdata/src/default_config/leading_whitespace.go.golden b/testdata/src/default_config/leading_whitespace.go.golden index 360c06a..8009257 100644 --- a/testdata/src/default_config/leading_whitespace.go.golden +++ b/testdata/src/default_config/leading_whitespace.go.golden @@ -12,8 +12,7 @@ func fn2() { // want "unnecessary whitespace decreases readability" } func fn3() { - // Space after comment - v5.0.0 BREAKING CHANGE - + // Space after comment // want "unnecessary whitespace decreases readability" fmt.Println("Hello, World") } diff --git a/wsl.go b/wsl.go index 60d44b8..1c1beb2 100644 --- a/wsl.go +++ b/wsl.go @@ -75,9 +75,23 @@ func (w *WSL) CheckUnnecessaryBlockLeadingNewline(body *ast.BlockStmt) { // If comment starts at the same line as the opening position it // should just extend the position for the fixer if needed. // func fn() { // This comment starts at the same line as LBrace - if commentStartLine == openingPosLine { + switch { + // The comment is on the same line as current opening position. + // E.g. func fn() { // A comment + case commentStartLine == openingPosLine: openingPos = comment.End() - } else { + // Opening position is the same as `{` and the comment is + // directly on the line after (no empty line) + case openingPosLine == w.lineFor(body.Lbrace) && + commentStartLine == w.lineFor(body.Lbrace)+1: + openingPos = comment.End() + // The opening position has been updated, it's another comment. + case openingPosLine != w.lineFor(body.Lbrace): + openingPos = comment.End() + // The opening position is still { and the comment is not + // directly above - it must be an empty line which shouldn't be + // there. + default: firstStmt = comment.Pos() } } From 2d4137b71e496499b21cddf12dc06a8fa244257e Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 17 Mar 2024 22:38:40 +0100 Subject: [PATCH 003/112] Add `Cursor`, explore `*ast.IfStmt` --- analyzer.go | 2 +- cursor.go | 53 +++++++++++++ testdata/src/wip/wip.go | 17 +++++ testdata/src/wip/wip.go.golden | 13 ++++ wsl.go | 132 +++++++++++++++++++++++++++++++-- wsl_test.go | 7 ++ 6 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 cursor.go create mode 100644 testdata/src/wip/wip.go create mode 100644 testdata/src/wip/wip.go.golden diff --git a/analyzer.go b/analyzer.go index 226b603..ae06fce 100644 --- a/analyzer.go +++ b/analyzer.go @@ -39,7 +39,7 @@ func (wa *wslAnalyzer) run(pass *analysis.Pass) (interface{}, error) { continue } - wsl := New(file, pass.Fset, wa.config) + wsl := New(file, pass, wa.config) wsl.Run() for pos, fix := range wsl.Issues { diff --git a/cursor.go b/cursor.go new file mode 100644 index 0000000..e335c21 --- /dev/null +++ b/cursor.go @@ -0,0 +1,53 @@ +package wsl + +import ( + "errors" + "go/ast" +) + +var ErrCursourOutObFounds = errors.New("out of bounds") + +type Cursor struct { + currentIdx int + savedIdx int + statements []ast.Stmt +} + +func NewCursor(i int, statements []ast.Stmt) *Cursor { + return &Cursor{ + currentIdx: i, + statements: statements, + } +} + +func (c *Cursor) Next() bool { + if c.currentIdx >= len(c.statements)-1 { + return false + } + + c.currentIdx++ + + return true +} + +func (c *Cursor) Previous() bool { + if c.currentIdx == 0 { + return false + } + + c.currentIdx-- + + return true +} + +func (c *Cursor) Stmt() ast.Stmt { + return c.statements[c.currentIdx] +} + +func (c *Cursor) Save() { + c.savedIdx = c.currentIdx +} + +func (c *Cursor) Reset() { + c.currentIdx = c.savedIdx +} diff --git a/testdata/src/wip/wip.go b/testdata/src/wip/wip.go new file mode 100644 index 0000000..de6dd93 --- /dev/null +++ b/testdata/src/wip/wip.go @@ -0,0 +1,17 @@ +package testpkg + +import "fmt" + +func fn1() { + one := 1 // want "missing whitespace decreases readability" + + if 1 == 1 { + fmt.Println(1) + } else if 1 == 2 { + fmt.Println(2) + } else { + fmt.Println(2) + } + + _ = one +} diff --git a/testdata/src/wip/wip.go.golden b/testdata/src/wip/wip.go.golden new file mode 100644 index 0000000..e2a988f --- /dev/null +++ b/testdata/src/wip/wip.go.golden @@ -0,0 +1,13 @@ +package testpkg + +import "fmt" + +func fn1() { + one := 1 // want "missing whitespace decreases readability" + + if 1 == 1 { + fmt.Println(2) + _ = one + } +} + diff --git a/wsl.go b/wsl.go index 1c1beb2..c82fbb0 100644 --- a/wsl.go +++ b/wsl.go @@ -1,8 +1,12 @@ package wsl import ( + "fmt" "go/ast" "go/token" + "go/types" + + "golang.org/x/tools/go/analysis" ) const ( @@ -25,15 +29,17 @@ type Issue struct { type WSL struct { File *ast.File Fset *token.FileSet + TypeInfo *types.Info Comments ast.CommentMap Issues map[token.Pos]Issue // TODO: When do we have multiple reports? } -func New(file *ast.File, fset *token.FileSet, _ *Configuration) *WSL { +func New(file *ast.File, pass *analysis.Pass, _ *Configuration) *WSL { return &WSL{ - Fset: fset, - File: file, - Issues: make(map[token.Pos]Issue), + Fset: pass.Fset, + File: file, + TypeInfo: pass.TypesInfo, + Issues: make(map[token.Pos]Issue), } } @@ -51,6 +57,110 @@ func (w *WSL) CheckFuncDecl(funcDecl *ast.FuncDecl) { } w.CheckUnnecessaryBlockLeadingNewline(funcDecl.Body) + w.CheckBlock(funcDecl.Body) +} + +func (w *WSL) CheckIfStmt(stmt *ast.IfStmt, cursor *Cursor) { + cursor.Save() + + n := w.numberOfStatementsAbove(cursor) + if n > 0 { + // TODO: Check if we're cuddled and if so that we do it as we should. Rules: + // * Only cuddle with _one_ assignment + // * Only cuddled with an assignment declared on the line above + // * OR a variable that is used first in the block + // * OR - if configured - anywhere in the block. + } + + // TODO: If we're _not_ cuddled, check if the previous variable implements + // an error and if that's used in the if statement. + var previous ast.Node + if cursor.Previous() { + previous = cursor.Stmt() + if pi, ok := previous.(*ast.AssignStmt); ok { + for _, lhs := range pi.Lhs { + v, ok := lhs.(*ast.Ident) + if !ok { + continue + } + + fmt.Println(w.implementsErr(v)) + } + } + } + + cursor.Reset() + + // if + w.CheckBlock(stmt.Body) + + switch v := stmt.Else.(type) { + // else-if + case *ast.IfStmt: + w.CheckIfStmt(v, cursor) + // else + case *ast.BlockStmt: + w.CheckBlock(v) + } +} + +func (w *WSL) CheckBlock(block *ast.BlockStmt) { + cursor := NewCursor(0, block.List) + for cursor.Next() { + fmt.Printf("%d: %T\n", cursor.currentIdx, cursor.Stmt()) + + //nolint:gocritic // This is not commented out code, it's examples + switch s := cursor.Stmt().(type) { + // if a {} else if b {} else {} + case *ast.IfStmt: + w.CheckIfStmt(s, cursor) + // for {} / for a; b; c {} + case *ast.ForStmt: + // for _, _ = range a {} + case *ast.RangeStmt: + // switch {} // switch a {} + case *ast.SwitchStmt: + // switch a.(type) {} + case *ast.TypeSwitchStmt: + // return a + case *ast.ReturnStmt: + // continue / break + case *ast.BranchStmt: + // var a + case *ast.DeclStmt: + // a := a + case *ast.AssignStmt: + // a++ / a-- + case *ast.IncDecStmt: + // defer func() {} + case *ast.DeferStmt: + // go func() {} + case *ast.GoStmt: + case *ast.ExprStmt: + default: + fmt.Printf("Not implemented: %T\n", s) + } + } +} + +// numberOfStatementsAbove will find out how many lines above statement at index +// `i` there is without any empty lines. +// lines. +func (w *WSL) numberOfStatementsAbove(cursor *Cursor) int { + statementsWithoutNewlines := 0 + + for i := cursor.currentIdx; i > 0; i-- { + thisStatementStartLine := w.lineFor(cursor.statements[i].Pos()) + previousStatementEndLine := w.lineFor(cursor.statements[i-1].End()) + + if thisStatementStartLine != previousStatementEndLine+1 { + break + } + + statementsWithoutNewlines++ + } + + return statementsWithoutNewlines } func (w *WSL) CheckUnnecessaryBlockLeadingNewline(body *ast.BlockStmt) { @@ -59,6 +169,7 @@ func (w *WSL) CheckUnnecessaryBlockLeadingNewline(body *ast.BlockStmt) { return } + lbraceLine := w.lineFor(body.Lbrace) openingPos := body.Lbrace + 1 firstStmt := body.List[0].Pos() @@ -82,11 +193,11 @@ func (w *WSL) CheckUnnecessaryBlockLeadingNewline(body *ast.BlockStmt) { openingPos = comment.End() // Opening position is the same as `{` and the comment is // directly on the line after (no empty line) - case openingPosLine == w.lineFor(body.Lbrace) && - commentStartLine == w.lineFor(body.Lbrace)+1: + case openingPosLine == lbraceLine && + commentStartLine == lbraceLine+1: openingPos = comment.End() // The opening position has been updated, it's another comment. - case openingPosLine != w.lineFor(body.Lbrace): + case openingPosLine != lbraceLine: openingPos = comment.End() // The opening position is still { and the comment is not // directly above - it must be an empty line which shouldn't be @@ -110,6 +221,13 @@ func (w *WSL) lineFor(pos token.Pos) int { return w.Fset.PositionFor(pos, false).Line } +func (w *WSL) implementsErr(node *ast.Ident) bool { + typeInfo := w.TypeInfo.TypeOf(node) + errorType := types.Universe.Lookup("error").Type().Underlying().(*types.Interface) + + return types.Implements(typeInfo, errorType) +} + func (w *WSL) addError(report, start, end token.Pos, message string) { issue, ok := w.Issues[report] if !ok { diff --git a/wsl_test.go b/wsl_test.go index e981390..01d6815 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -12,3 +12,10 @@ func TestDefaultConfig(t *testing.T) { analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "default_config") } + +func TestWIP(t *testing.T) { + testdata := analysistest.TestData() + analyzer := NewAnalyzer(nil) + + analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "wip") +} From b52dfe07d69a5eb3b8da85d72a1bcb352b40785c Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 18 Mar 2024 22:45:18 +0100 Subject: [PATCH 004/112] Handle force error checking --- cursor.go | 2 +- testdata/src/wip/wip.go | 15 +- testdata/src/wip/wip.go.golden | 15 +- .../force_cuddle_errcheck.go | 20 +++ .../force_cuddle_errcheck.go.gold | 22 +++ wsl.go | 132 ++++++++++++++---- 6 files changed, 160 insertions(+), 46 deletions(-) create mode 100644 testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go create mode 100644 testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go.gold diff --git a/cursor.go b/cursor.go index e335c21..1a3df9c 100644 --- a/cursor.go +++ b/cursor.go @@ -31,7 +31,7 @@ func (c *Cursor) Next() bool { } func (c *Cursor) Previous() bool { - if c.currentIdx == 0 { + if c.currentIdx <= 0 { return false } diff --git a/testdata/src/wip/wip.go b/testdata/src/wip/wip.go index de6dd93..ab6a1b8 100644 --- a/testdata/src/wip/wip.go +++ b/testdata/src/wip/wip.go @@ -1,17 +1,10 @@ package testpkg -import "fmt" - func fn1() { - one := 1 // want "missing whitespace decreases readability" + two := 2 - if 1 == 1 { - fmt.Println(1) - } else if 1 == 2 { - fmt.Println(2) - } else { - fmt.Println(2) + one := 1 + if two == 2 { // want "missing whitespace decreases readability" + panic(err) } - - _ = one } diff --git a/testdata/src/wip/wip.go.golden b/testdata/src/wip/wip.go.golden index e2a988f..b7eea46 100644 --- a/testdata/src/wip/wip.go.golden +++ b/testdata/src/wip/wip.go.golden @@ -1,13 +1,14 @@ -package testpkg -import "fmt" +package testpkg func fn1() { - one := 1 // want "missing whitespace decreases readability" + two := 2 + + one := 1 - if 1 == 1 { - fmt.Println(2) - _ = one + if two == 2 { // want "missing whitespace decreases readability" + panic(err) } -} + _ = one +} diff --git a/testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go b/testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go new file mode 100644 index 0000000..6bd12f1 --- /dev/null +++ b/testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go @@ -0,0 +1,20 @@ +package testpkg + +import "errors" + +func fn1() { + err := errors.New("x") + if err != nil { // want "unnecessary whitespace decreases readability" + panic(err) + } +} + +func fn2() { + one := 1 + err := errors.New("x") + if err != nil { // want "unnecessary whitespace decreases readability" + panic(err) + } + + _ = 1 +} diff --git a/testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go.gold b/testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go.gold new file mode 100644 index 0000000..20c0419 --- /dev/null +++ b/testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go.gold @@ -0,0 +1,22 @@ +package testpkg + +import "errors" + +func fn1() { + err := errors.New("x") + if err != nil { // want "unnecessary whitespace decreases readability" + panic(err) + } +} + +func fn2() { + one := 1 + + err := errors.New("x") + if err != nil { // want "unnecessary whitespace decreases readability" + panic(err) + } + + _ = 1 +} + diff --git a/wsl.go b/wsl.go index c82fbb0..7928ba7 100644 --- a/wsl.go +++ b/wsl.go @@ -5,7 +5,9 @@ import ( "go/ast" "go/token" "go/types" + "slices" + "github.com/davecgh/go-spew/spew" "golang.org/x/tools/go/analysis" ) @@ -46,12 +48,12 @@ func New(file *ast.File, pass *analysis.Pass, _ *Configuration) *WSL { func (w *WSL) Run() { for _, decl := range w.File.Decls { if funcDecl, ok := decl.(*ast.FuncDecl); ok { - w.CheckFuncDecl(funcDecl) + w.CheckFunc(funcDecl) } } } -func (w *WSL) CheckFuncDecl(funcDecl *ast.FuncDecl) { +func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { if funcDecl.Body == nil { return } @@ -60,8 +62,8 @@ func (w *WSL) CheckFuncDecl(funcDecl *ast.FuncDecl) { w.CheckBlock(funcDecl.Body) } -func (w *WSL) CheckIfStmt(stmt *ast.IfStmt, cursor *Cursor) { - cursor.Save() +func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { + current := cursor.currentIdx n := w.numberOfStatementsAbove(cursor) if n > 0 { @@ -74,22 +76,49 @@ func (w *WSL) CheckIfStmt(stmt *ast.IfStmt, cursor *Cursor) { // TODO: If we're _not_ cuddled, check if the previous variable implements // an error and if that's used in the if statement. - var previous ast.Node - if cursor.Previous() { - previous = cursor.Stmt() - if pi, ok := previous.(*ast.AssignStmt); ok { - for _, lhs := range pi.Lhs { - v, ok := lhs.(*ast.Ident) - if !ok { - continue - } - - fmt.Println(w.implementsErr(v)) + currentIdents := allIdents(cursor.Stmt()) + + shouldCuddleErr := true // TODO(config): Should be configurable + if shouldCuddleErr && n == 0 && cursor.Previous() { + previousIdents := allIdents(cursor.Stmt()) + intersects := identIntersection(currentIdents, previousIdents) + + if slices.ContainsFunc(intersects, func(ident *ast.Ident) bool { + return w.implementsErr(ident) + }) { + w.addError( + stmt.Pos(), + cursor.Stmt().End(), + stmt.Pos(), + MessageRemoveWhitespace, + ) + + // If we add the error at the same position but with a different fix + // range, only the fix range will be updated. + // + // a := 1 + // err := fn() + // + // if err != nil {} + // + // Should become + // + // a := 1 + // + // err := fn() + // if err != nil {} + if w.numberOfStatementsAbove(cursor) > 0 { + w.addError( + stmt.Pos(), + cursor.Stmt().Pos(), + cursor.Stmt().Pos(), + MessageRemoveWhitespace, + ) } } } - cursor.Reset() + cursor.currentIdx = current // if w.CheckBlock(stmt.Body) @@ -97,7 +126,7 @@ func (w *WSL) CheckIfStmt(stmt *ast.IfStmt, cursor *Cursor) { switch v := stmt.Else.(type) { // else-if case *ast.IfStmt: - w.CheckIfStmt(v, cursor) + w.CheckIf(v, cursor) // else case *ast.BlockStmt: w.CheckBlock(v) @@ -113,7 +142,7 @@ func (w *WSL) CheckBlock(block *ast.BlockStmt) { switch s := cursor.Stmt().(type) { // if a {} else if b {} else {} case *ast.IfStmt: - w.CheckIfStmt(s, cursor) + w.CheckIf(s, cursor) // for {} / for a; b; c {} case *ast.ForStmt: // for _, _ = range a {} @@ -143,20 +172,22 @@ func (w *WSL) CheckBlock(block *ast.BlockStmt) { } } -// numberOfStatementsAbove will find out how many lines above statement at index -// `i` there is without any empty lines. -// lines. +// numberOfStatementsAbove will find out how many lines above the cursor's +// current statement there is without any newlines between. func (w *WSL) numberOfStatementsAbove(cursor *Cursor) int { - statementsWithoutNewlines := 0 + cursor.Save() + defer cursor.Reset() - for i := cursor.currentIdx; i > 0; i-- { - thisStatementStartLine := w.lineFor(cursor.statements[i].Pos()) - previousStatementEndLine := w.lineFor(cursor.statements[i-1].End()) + statementsWithoutNewlines := 0 + currentStmtStartLine := w.lineFor(cursor.Stmt().Pos()) - if thisStatementStartLine != previousStatementEndLine+1 { + for cursor.Previous() { + previousStmtEndLine := w.lineFor(cursor.Stmt().End()) + if previousStmtEndLine != currentStmtStartLine-1 { break } + currentStmtStartLine = w.lineFor(cursor.Stmt().Pos()) statementsWithoutNewlines++ } @@ -223,7 +254,11 @@ func (w *WSL) lineFor(pos token.Pos) int { func (w *WSL) implementsErr(node *ast.Ident) bool { typeInfo := w.TypeInfo.TypeOf(node) - errorType := types.Universe.Lookup("error").Type().Underlying().(*types.Interface) + + errorType, ok := types.Universe.Lookup("error").Type().Underlying().(*types.Interface) + if !ok { + return false + } return types.Implements(typeInfo, errorType) } @@ -244,3 +279,46 @@ func (w *WSL) addError(report, start, end token.Pos, message string) { w.Issues[report] = issue } + +func allIdents(node ast.Node) []*ast.Ident { + idents := []*ast.Ident{} + + if node == nil { + return idents + } + + switch n := node.(type) { + case *ast.Ident: + return []*ast.Ident{n} + case *ast.AssignStmt: + for _, lhs := range n.Lhs { + idents = append(idents, allIdents(lhs)...) + } + case *ast.IfStmt: + idents = append(idents, allIdents(n.Cond)...) + // TODO: idents = append(idents, allIdents(n.Else)...) + case *ast.BinaryExpr: + idents = append(idents, allIdents(n.X)...) + idents = append(idents, allIdents(n.Y)...) + case *ast.BasicLit: + default: + spew.Dump(node) + fmt.Printf("%T\n", node) + } + + return idents +} + +func identIntersection(a, b []*ast.Ident) []*ast.Ident { + intersects := []*ast.Ident{} + + for _, as := range a { + for _, bs := range b { + if as.Name == bs.Name { + intersects = append(intersects, as) + } + } + } + + return intersects +} From 731e9ea053eed95bf1d281fba701879f0c20681f Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 23 Mar 2024 19:40:14 +0100 Subject: [PATCH 005/112] Handle if statements --- testdata/src/default_config/if.go | 27 ++++++++++ testdata/src/default_config/if.go.golden | 29 ++++++++++ testdata/src/wip/wip.go | 9 ---- testdata/src/wip/wip.go.golden | 13 ----- .../if_errcheck.go} | 0 .../if_errcheck.go.golden} | 0 wsl.go | 54 ++++++++++++++----- 7 files changed, 97 insertions(+), 35 deletions(-) create mode 100644 testdata/src/default_config/if.go create mode 100644 testdata/src/default_config/if.go.golden rename testdata/src/with_config/{force_cuddle_errcheck/force_cuddle_errcheck.go => if_errcheck/if_errcheck.go} (100%) rename testdata/src/with_config/{force_cuddle_errcheck/force_cuddle_errcheck.go.gold => if_errcheck/if_errcheck.go.golden} (100%) diff --git a/testdata/src/default_config/if.go b/testdata/src/default_config/if.go new file mode 100644 index 0000000..e1ae8aa --- /dev/null +++ b/testdata/src/default_config/if.go @@ -0,0 +1,27 @@ +package testpkg + +import "errors" + +func fn1() { + one := 1 + two := 2 // want "missing whitespace decreases readability" + if two == 2 { + panic(err) + } +} + +func fn2() { + two := 2 + one := 1 + if two == 2 { // want "missing whitespace decreases readability" + panic(err) + } +} + +func fn3() { + err := errors.New("error") + + if err != nil { + panic(err) + } +} diff --git a/testdata/src/default_config/if.go.golden b/testdata/src/default_config/if.go.golden new file mode 100644 index 0000000..81d00b9 --- /dev/null +++ b/testdata/src/default_config/if.go.golden @@ -0,0 +1,29 @@ +package testpkg + +import "errors" + +func fn1() { + one := 1 + + two := 2 // want "missing whitespace decreases readability" + if two == 2 { + panic(err) + } +} + +func fn2() { + two := 2 + one := 1 + + if two == 2 { // want "missing whitespace decreases readability" + panic(err) + } +} + +func fn3() { + err := errors.New("error") + + if err != nil { + panic(err) + } +} diff --git a/testdata/src/wip/wip.go b/testdata/src/wip/wip.go index ab6a1b8..a21e3cf 100644 --- a/testdata/src/wip/wip.go +++ b/testdata/src/wip/wip.go @@ -1,10 +1 @@ package testpkg - -func fn1() { - two := 2 - - one := 1 - if two == 2 { // want "missing whitespace decreases readability" - panic(err) - } -} diff --git a/testdata/src/wip/wip.go.golden b/testdata/src/wip/wip.go.golden index b7eea46..a21e3cf 100644 --- a/testdata/src/wip/wip.go.golden +++ b/testdata/src/wip/wip.go.golden @@ -1,14 +1 @@ - package testpkg - -func fn1() { - two := 2 - - one := 1 - - if two == 2 { // want "missing whitespace decreases readability" - panic(err) - } - - _ = one -} diff --git a/testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go b/testdata/src/with_config/if_errcheck/if_errcheck.go similarity index 100% rename from testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go rename to testdata/src/with_config/if_errcheck/if_errcheck.go diff --git a/testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go.gold b/testdata/src/with_config/if_errcheck/if_errcheck.go.golden similarity index 100% rename from testdata/src/with_config/force_cuddle_errcheck/force_cuddle_errcheck.go.gold rename to testdata/src/with_config/if_errcheck/if_errcheck.go.golden diff --git a/wsl.go b/wsl.go index 7928ba7..990df6e 100644 --- a/wsl.go +++ b/wsl.go @@ -16,7 +16,10 @@ const ( MessageRemoveWhitespace = "unnecessary whitespace decreases readability" ) -type Configuration struct{} +type Configuration struct { + // Require no newline between error assignment and error check. + Errcheck bool +} type FixRange struct { FixRangeStart token.Pos @@ -34,6 +37,7 @@ type WSL struct { TypeInfo *types.Info Comments ast.CommentMap Issues map[token.Pos]Issue // TODO: When do we have multiple reports? + Config Configuration } func New(file *ast.File, pass *analysis.Pass, _ *Configuration) *WSL { @@ -42,6 +46,7 @@ func New(file *ast.File, pass *analysis.Pass, _ *Configuration) *WSL { File: file, TypeInfo: pass.TypesInfo, Issues: make(map[token.Pos]Issue), + Config: Configuration{}, } } @@ -63,26 +68,49 @@ func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { } func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { + // TODO: This seems finicky, we don't want to do this. current := cursor.currentIdx - n := w.numberOfStatementsAbove(cursor) - if n > 0 { - // TODO: Check if we're cuddled and if so that we do it as we should. Rules: - // * Only cuddle with _one_ assignment - // * Only cuddled with an assignment declared on the line above - // * OR a variable that is used first in the block - // * OR - if configured - anywhere in the block. - } - // TODO: If we're _not_ cuddled, check if the previous variable implements // an error and if that's used in the if statement. currentIdents := allIdents(cursor.Stmt()) - shouldCuddleErr := true // TODO(config): Should be configurable - if shouldCuddleErr && n == 0 && cursor.Previous() { - previousIdents := allIdents(cursor.Stmt()) + previousIdents := []*ast.Ident{} + if cursor.Previous() { + previousIdents = allIdents(cursor.Stmt()) + } + + n := w.numberOfStatementsAbove(cursor) + if n > 0 { intersects := identIntersection(currentIdents, previousIdents) + // No idents above share name with one in the if statement. + if len(intersects) == 0 { + w.addError( + stmt.Pos(), + stmt.Pos(), + stmt.Pos(), + MessageAddWhitespace, + ) + } + + // Idents on the line above exist in the current condition so that + // should remain cuddled. + if len(intersects) > 0 { + w.addError( + cursor.Stmt().Pos(), + cursor.Stmt().Pos(), + cursor.Stmt().Pos(), + MessageAddWhitespace, + ) + } + // TODO: Features: + // * Allow idents that is used first in the block + // * OR - if configured - anywhere in the block. + } + + if w.Config.Errcheck && n == 0 && len(previousIdents) > 0 { + intersects := identIntersection(currentIdents, previousIdents) if slices.ContainsFunc(intersects, func(ident *ast.Ident) bool { return w.implementsErr(ident) }) { From bd1297ebd3c12d9977b37e1d0b40c805b77c7627 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 23 Mar 2024 20:04:55 +0100 Subject: [PATCH 006/112] Improve cursor states, add test --- cursor.go | 13 ++++++++++--- cursor_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 +++ go.sum | 7 +++++++ wsl.go | 7 ++----- 5 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 cursor_test.go diff --git a/cursor.go b/cursor.go index 1a3df9c..7dcd813 100644 --- a/cursor.go +++ b/cursor.go @@ -9,14 +9,15 @@ var ErrCursourOutObFounds = errors.New("out of bounds") type Cursor struct { currentIdx int - savedIdx int statements []ast.Stmt + saves []int } func NewCursor(i int, statements []ast.Stmt) *Cursor { return &Cursor{ currentIdx: i, statements: statements, + saves: []int{}, } } @@ -45,9 +46,15 @@ func (c *Cursor) Stmt() ast.Stmt { } func (c *Cursor) Save() { - c.savedIdx = c.currentIdx + c.saves = append(c.saves, c.currentIdx) } func (c *Cursor) Reset() { - c.currentIdx = c.savedIdx + if len(c.saves) == 0 { + return + } + + idx := c.saves[len(c.saves)-1] + c.saves = c.saves[:len(c.saves)-1] + c.currentIdx = idx } diff --git a/cursor_test.go b/cursor_test.go new file mode 100644 index 0000000..229ebfd --- /dev/null +++ b/cursor_test.go @@ -0,0 +1,49 @@ +package wsl + +import ( + "go/ast" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSavestate(t *testing.T) { + cursor := NewCursor(0, []ast.Stmt{ + &ast.AssignStmt{}, + &ast.AssignStmt{}, + &ast.AssignStmt{}, + &ast.AssignStmt{}, + &ast.AssignStmt{}, + &ast.AssignStmt{}, + &ast.AssignStmt{}, + }) + + cursor.Save() + cursor.Next() + cursor.Next() + + func() { + cursor.Save() + defer cursor.Reset() + + cursor.Next() + cursor.Next() + + func() { + cursor.Save() + defer cursor.Reset() + + cursor.Next() + cursor.Next() + + assert.Equal(t, 6, cursor.currentIdx) + }() + + assert.Equal(t, 4, cursor.currentIdx) + }() + + assert.Equal(t, 2, cursor.currentIdx) + + cursor.Reset() + assert.Equal(t, 0, cursor.currentIdx) +} diff --git a/go.mod b/go.mod index 36f900f..6702604 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,9 @@ require golang.org/x/tools v0.19.0 require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect golang.org/x/mod v0.16.0 // indirect golang.org/x/sys v0.18.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ebd3f89..b451e5e 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= @@ -13,3 +17,6 @@ golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/wsl.go b/wsl.go index 990df6e..52482a2 100644 --- a/wsl.go +++ b/wsl.go @@ -68,11 +68,8 @@ func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { } func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { - // TODO: This seems finicky, we don't want to do this. - current := cursor.currentIdx + cursor.Save() - // TODO: If we're _not_ cuddled, check if the previous variable implements - // an error and if that's used in the if statement. currentIdents := allIdents(cursor.Stmt()) previousIdents := []*ast.Ident{} @@ -146,7 +143,7 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { } } - cursor.currentIdx = current + cursor.Reset() // if w.CheckBlock(stmt.Body) From 35f46404ef7fe36e834a61532ab28615444d6cc2 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 4 May 2024 22:48:49 +0200 Subject: [PATCH 007/112] WIP: Cursor --- cursor.go | 14 ++++++++++++++ testdata/src/wip/wip.go | 9 +++++++++ testdata/src/wip/wip.go.golden | 10 ++++++++++ wsl.go | 5 +++++ 4 files changed, 38 insertions(+) diff --git a/cursor.go b/cursor.go index 7dcd813..76fb18b 100644 --- a/cursor.go +++ b/cursor.go @@ -41,6 +41,20 @@ func (c *Cursor) Previous() bool { return true } +func (c *Cursor) PeekNext() bool { + c.Save() + defer c.Reset() + + return c.Next() +} + +func (c *Cursor) PeekPrevious() bool { + c.Save() + defer c.Reset() + + return c.Previous() +} + func (c *Cursor) Stmt() ast.Stmt { return c.statements[c.currentIdx] } diff --git a/testdata/src/wip/wip.go b/testdata/src/wip/wip.go index a21e3cf..c33c0d7 100644 --- a/testdata/src/wip/wip.go +++ b/testdata/src/wip/wip.go @@ -1 +1,10 @@ package testpkg + +func fn1() { + if foo := 1; foo != 2 { + panic(foo) + } + if foo := 2; foo != 2 { // want "missing whitespace decreases readability" + panic(bar) + } +} diff --git a/testdata/src/wip/wip.go.golden b/testdata/src/wip/wip.go.golden index a21e3cf..2f6f6ee 100644 --- a/testdata/src/wip/wip.go.golden +++ b/testdata/src/wip/wip.go.golden @@ -1 +1,11 @@ package testpkg + +func fn1() { + if foo := 1; foo != 2 { + panic(foo) + } + + if foo := 2; foo != 2 { // want "missing whitespace decreases readability" + panic(bar) + } +} diff --git a/wsl.go b/wsl.go index 52482a2..77b707b 100644 --- a/wsl.go +++ b/wsl.go @@ -75,12 +75,17 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { previousIdents := []*ast.Ident{} if cursor.Previous() { previousIdents = allIdents(cursor.Stmt()) + cursor.Next() } n := w.numberOfStatementsAbove(cursor) if n > 0 { intersects := identIntersection(currentIdents, previousIdents) + fmt.Println(currentIdents) + fmt.Println(previousIdents) + fmt.Println(intersects) + // No idents above share name with one in the if statement. if len(intersects) == 0 { w.addError( From 58eaa029badada91daac52deabc92d0f02004828 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 6 Jun 2024 22:51:54 +0200 Subject: [PATCH 008/112] Fix multiple if --- wsl.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wsl.go b/wsl.go index 77b707b..f3fec95 100644 --- a/wsl.go +++ b/wsl.go @@ -82,10 +82,6 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { if n > 0 { intersects := identIntersection(currentIdents, previousIdents) - fmt.Println(currentIdents) - fmt.Println(previousIdents) - fmt.Println(intersects) - // No idents above share name with one in the if statement. if len(intersects) == 0 { w.addError( @@ -99,6 +95,8 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { // Idents on the line above exist in the current condition so that // should remain cuddled. if len(intersects) > 0 { + cursor.Previous() + w.addError( cursor.Stmt().Pos(), cursor.Stmt().Pos(), From 6d6e70e75be26e1267962cf20b51d92bfce97877 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 6 Jun 2024 22:52:08 +0200 Subject: [PATCH 009/112] Simplify resetting cursor --- cursor.go | 25 ++++++++----------------- cursor_test.go | 12 ++++++------ wsl.go | 8 ++++---- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/cursor.go b/cursor.go index 76fb18b..dec379a 100644 --- a/cursor.go +++ b/cursor.go @@ -10,14 +10,12 @@ var ErrCursourOutObFounds = errors.New("out of bounds") type Cursor struct { currentIdx int statements []ast.Stmt - saves []int } func NewCursor(i int, statements []ast.Stmt) *Cursor { return &Cursor{ currentIdx: i, statements: statements, - saves: []int{}, } } @@ -42,15 +40,15 @@ func (c *Cursor) Previous() bool { } func (c *Cursor) PeekNext() bool { - c.Save() - defer c.Reset() + reset := c.Save() + defer reset() return c.Next() } func (c *Cursor) PeekPrevious() bool { - c.Save() - defer c.Reset() + reset := c.Save() + defer reset() return c.Previous() } @@ -59,16 +57,9 @@ func (c *Cursor) Stmt() ast.Stmt { return c.statements[c.currentIdx] } -func (c *Cursor) Save() { - c.saves = append(c.saves, c.currentIdx) -} - -func (c *Cursor) Reset() { - if len(c.saves) == 0 { - return +func (c *Cursor) Save() func() { + idx := c.currentIdx + return func() { + c.currentIdx = idx } - - idx := c.saves[len(c.saves)-1] - c.saves = c.saves[:len(c.saves)-1] - c.currentIdx = idx } diff --git a/cursor_test.go b/cursor_test.go index 229ebfd..b15d9a7 100644 --- a/cursor_test.go +++ b/cursor_test.go @@ -18,20 +18,20 @@ func TestSavestate(t *testing.T) { &ast.AssignStmt{}, }) - cursor.Save() + reset := cursor.Save() cursor.Next() cursor.Next() func() { - cursor.Save() - defer cursor.Reset() + reset := cursor.Save() + defer reset() cursor.Next() cursor.Next() func() { - cursor.Save() - defer cursor.Reset() + reset := cursor.Save() + defer reset() cursor.Next() cursor.Next() @@ -44,6 +44,6 @@ func TestSavestate(t *testing.T) { assert.Equal(t, 2, cursor.currentIdx) - cursor.Reset() + reset() assert.Equal(t, 0, cursor.currentIdx) } diff --git a/wsl.go b/wsl.go index f3fec95..a64bca1 100644 --- a/wsl.go +++ b/wsl.go @@ -68,7 +68,7 @@ func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { } func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { - cursor.Save() + reset := cursor.Save() currentIdents := allIdents(cursor.Stmt()) @@ -146,7 +146,7 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { } } - cursor.Reset() + reset() // if w.CheckBlock(stmt.Body) @@ -203,8 +203,8 @@ func (w *WSL) CheckBlock(block *ast.BlockStmt) { // numberOfStatementsAbove will find out how many lines above the cursor's // current statement there is without any newlines between. func (w *WSL) numberOfStatementsAbove(cursor *Cursor) int { - cursor.Save() - defer cursor.Reset() + reset := cursor.Save() + defer reset() statementsWithoutNewlines := 0 currentStmtStartLine := w.lineFor(cursor.Stmt().Pos()) From 1e3a52a4fbe54c662e4b61ef28c0676160457058 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 6 Jun 2024 23:38:16 +0200 Subject: [PATCH 010/112] Improve handling of if --- testdata/src/default_config/if.go | 9 ++ testdata/src/default_config/if.go.golden | 10 ++ .../src/default_config/leading_whitespace.go | 7 + .../leading_whitespace.go.golden | 6 + testdata/src/wip/wip.go | 9 +- testdata/src/wip/wip.go.golden | 10 +- wsl.go | 128 +++++++++++------- 7 files changed, 110 insertions(+), 69 deletions(-) diff --git a/testdata/src/default_config/if.go b/testdata/src/default_config/if.go index e1ae8aa..c4775e6 100644 --- a/testdata/src/default_config/if.go +++ b/testdata/src/default_config/if.go @@ -25,3 +25,12 @@ func fn3() { panic(err) } } + +func fn4() { + if foo := 1; foo != 2 { + panic(foo) + } + if foo := 2; foo != 2 { // want "missing whitespace decreases readability" + panic(bar) + } +} diff --git a/testdata/src/default_config/if.go.golden b/testdata/src/default_config/if.go.golden index 81d00b9..da1bdca 100644 --- a/testdata/src/default_config/if.go.golden +++ b/testdata/src/default_config/if.go.golden @@ -27,3 +27,13 @@ func fn3() { panic(err) } } + +func fn4() { + if foo := 1; foo != 2 { + panic(foo) + } + + if foo := 2; foo != 2 { // want "missing whitespace decreases readability" + panic(bar) + } +} diff --git a/testdata/src/default_config/leading_whitespace.go b/testdata/src/default_config/leading_whitespace.go index fe9808a..ce0a069 100644 --- a/testdata/src/default_config/leading_whitespace.go +++ b/testdata/src/default_config/leading_whitespace.go @@ -27,3 +27,10 @@ func fn5() { // Comment without space before or after. fmt.Println("Hello, World") } + +func fn6() { + if true { // want "unnecessary whitespace decreases readability" + + _ = 1 + } +} diff --git a/testdata/src/default_config/leading_whitespace.go.golden b/testdata/src/default_config/leading_whitespace.go.golden index 8009257..f973c64 100644 --- a/testdata/src/default_config/leading_whitespace.go.golden +++ b/testdata/src/default_config/leading_whitespace.go.golden @@ -24,3 +24,9 @@ func fn5() { // Comment without space before or after. fmt.Println("Hello, World") } + +func fn6() { + if true { // want "unnecessary whitespace decreases readability" + _ = 1 + } +} diff --git a/testdata/src/wip/wip.go b/testdata/src/wip/wip.go index c33c0d7..e9ad075 100644 --- a/testdata/src/wip/wip.go +++ b/testdata/src/wip/wip.go @@ -1,10 +1,3 @@ package testpkg -func fn1() { - if foo := 1; foo != 2 { - panic(foo) - } - if foo := 2; foo != 2 { // want "missing whitespace decreases readability" - panic(bar) - } -} +func fn1() {} diff --git a/testdata/src/wip/wip.go.golden b/testdata/src/wip/wip.go.golden index 2f6f6ee..e9ad075 100644 --- a/testdata/src/wip/wip.go.golden +++ b/testdata/src/wip/wip.go.golden @@ -1,11 +1,3 @@ package testpkg -func fn1() { - if foo := 1; foo != 2 { - panic(foo) - } - - if foo := 2; foo != 2 { // want "missing whitespace decreases readability" - panic(bar) - } -} +func fn1() {} diff --git a/wsl.go b/wsl.go index a64bca1..b621d5b 100644 --- a/wsl.go +++ b/wsl.go @@ -71,32 +71,51 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { reset := cursor.Save() currentIdents := allIdents(cursor.Stmt()) - previousIdents := []*ast.Ident{} + + var previousNode ast.Node = nil + if cursor.Previous() { - previousIdents = allIdents(cursor.Stmt()) - cursor.Next() + previousNode = cursor.Stmt() + previousIdents = allIdents(previousNode) + + cursor.Next() // Move forward again } + _, prevIsAssign := previousNode.(*ast.AssignStmt) + _, prevIsDecl := previousNode.(*ast.DeclStmt) n := w.numberOfStatementsAbove(cursor) + if n > 0 { - intersects := identIntersection(currentIdents, previousIdents) + if prevIsAssign || prevIsDecl { + intersects := identIntersection(currentIdents, previousIdents) - // No idents above share name with one in the if statement. - if len(intersects) == 0 { - w.addError( - stmt.Pos(), - stmt.Pos(), - stmt.Pos(), - MessageAddWhitespace, - ) - } + // No idents above share name with one in the if statement. + if len(intersects) == 0 { + w.addError( + stmt.Pos(), + stmt.Pos(), + stmt.Pos(), + MessageAddWhitespace, + ) + } - // Idents on the line above exist in the current condition so that - // should remain cuddled. - if len(intersects) > 0 { - cursor.Previous() + // Idents on the line above exist in the current condition so that + // should remain cuddled. + if len(intersects) > 0 { + cursor.Previous() + w.addError( + cursor.Stmt().Pos(), + cursor.Stmt().Pos(), + cursor.Stmt().Pos(), + MessageAddWhitespace, + ) + } + // TODO: Features: + // * Allow idents that is used first in the block + // * OR - if configured - anywhere in the block. + } else { w.addError( cursor.Stmt().Pos(), cursor.Stmt().Pos(), @@ -104,9 +123,6 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { MessageAddWhitespace, ) } - // TODO: Features: - // * Allow idents that is used first in the block - // * OR - if configured - anywhere in the block. } if w.Config.Errcheck && n == 0 && len(previousIdents) > 0 { @@ -148,6 +164,8 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { reset() + w.CheckUnnecessaryBlockLeadingNewline(stmt.Body) + // if w.CheckBlock(stmt.Body) @@ -157,46 +175,52 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { w.CheckIf(v, cursor) // else case *ast.BlockStmt: + w.CheckUnnecessaryBlockLeadingNewline(v) w.CheckBlock(v) } } func (w *WSL) CheckBlock(block *ast.BlockStmt) { - cursor := NewCursor(0, block.List) + cursor := NewCursor(-1, block.List) for cursor.Next() { fmt.Printf("%d: %T\n", cursor.currentIdx, cursor.Stmt()) + w.CheckStmt(cursor.Stmt(), cursor) + } +} - //nolint:gocritic // This is not commented out code, it's examples - switch s := cursor.Stmt().(type) { - // if a {} else if b {} else {} - case *ast.IfStmt: - w.CheckIf(s, cursor) - // for {} / for a; b; c {} - case *ast.ForStmt: - // for _, _ = range a {} - case *ast.RangeStmt: - // switch {} // switch a {} - case *ast.SwitchStmt: - // switch a.(type) {} - case *ast.TypeSwitchStmt: - // return a - case *ast.ReturnStmt: - // continue / break - case *ast.BranchStmt: - // var a - case *ast.DeclStmt: - // a := a - case *ast.AssignStmt: - // a++ / a-- - case *ast.IncDecStmt: - // defer func() {} - case *ast.DeferStmt: - // go func() {} - case *ast.GoStmt: - case *ast.ExprStmt: - default: - fmt.Printf("Not implemented: %T\n", s) - } +func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { + //nolint:gocritic // This is not commented out code, it's examples + switch s := stmt.(type) { + // if a {} else if b {} else {} + case *ast.IfStmt: + w.CheckIf(s, cursor) + // for {} / for a; b; c {} + case *ast.ForStmt: + // for _, _ = range a {} + case *ast.RangeStmt: + // switch {} // switch a {} + case *ast.SwitchStmt: + // switch a.(type) {} + case *ast.TypeSwitchStmt: + // return a + case *ast.ReturnStmt: + // continue / break + case *ast.BranchStmt: + // var a + case *ast.DeclStmt: + // a := a + case *ast.AssignStmt: + // a++ / a-- + case *ast.IncDecStmt: + // defer func() {} + case *ast.DeferStmt: + // go func() {} + case *ast.GoStmt: + case *ast.ExprStmt: + case *ast.BlockStmt: + w.CheckBlock(s) + default: + fmt.Printf("Not implemented: %T\n", s) } } From a793bc0358b0d180560149e58cb8e7f3f61a67d3 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 23 Jun 2024 16:07:08 +0200 Subject: [PATCH 011/112] Add support to force error cuddlig --- .../with_config/if_errcheck/if_errcheck.go | 18 +++++++++- .../if_errcheck/if_errcheck.go.golden | 16 ++++++++- wsl.go | 33 ++++++++++--------- wsl_test.go | 29 ++++++++++++++-- 4 files changed, 76 insertions(+), 20 deletions(-) diff --git a/testdata/src/with_config/if_errcheck/if_errcheck.go b/testdata/src/with_config/if_errcheck/if_errcheck.go index 6bd12f1..8035538 100644 --- a/testdata/src/with_config/if_errcheck/if_errcheck.go +++ b/testdata/src/with_config/if_errcheck/if_errcheck.go @@ -4,6 +4,7 @@ import "errors" func fn1() { err := errors.New("x") + if err != nil { // want "unnecessary whitespace decreases readability" panic(err) } @@ -12,9 +13,24 @@ func fn1() { func fn2() { one := 1 err := errors.New("x") + if err != nil { // want "unnecessary whitespace decreases readability" panic(err) } +} - _ = 1 +func fn3() { + one := 1 + err := errors.New("x") // want "missing whitespace decreases readability" + if err != nil { + panic(err) + } +} + +func fn4() { + err := "not an error" + + if err != nil { + panic(err) + } } diff --git a/testdata/src/with_config/if_errcheck/if_errcheck.go.golden b/testdata/src/with_config/if_errcheck/if_errcheck.go.golden index 20c0419..9d36536 100644 --- a/testdata/src/with_config/if_errcheck/if_errcheck.go.golden +++ b/testdata/src/with_config/if_errcheck/if_errcheck.go.golden @@ -16,7 +16,21 @@ func fn2() { if err != nil { // want "unnecessary whitespace decreases readability" panic(err) } +} + +func fn3() { + one := 1 - _ = 1 + err := errors.New("x") // want "missing whitespace decreases readability" + if err != nil { + panic(err) + } } +func fn4() { + err := "not an error" + + if err != nil { + panic(err) + } +} diff --git a/wsl.go b/wsl.go index b621d5b..4d43b5f 100644 --- a/wsl.go +++ b/wsl.go @@ -37,16 +37,16 @@ type WSL struct { TypeInfo *types.Info Comments ast.CommentMap Issues map[token.Pos]Issue // TODO: When do we have multiple reports? - Config Configuration + Config *Configuration } -func New(file *ast.File, pass *analysis.Pass, _ *Configuration) *WSL { +func New(file *ast.File, pass *analysis.Pass, cfg *Configuration) *WSL { return &WSL{ Fset: pass.Fset, File: file, TypeInfo: pass.TypesInfo, Issues: make(map[token.Pos]Issue), - Config: Configuration{}, + Config: cfg, } } @@ -73,7 +73,7 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { currentIdents := allIdents(cursor.Stmt()) previousIdents := []*ast.Ident{} - var previousNode ast.Node = nil + var previousNode ast.Node if cursor.Previous() { previousNode = cursor.Stmt() @@ -103,12 +103,10 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { // Idents on the line above exist in the current condition so that // should remain cuddled. if len(intersects) > 0 { - cursor.Previous() - w.addError( - cursor.Stmt().Pos(), - cursor.Stmt().Pos(), - cursor.Stmt().Pos(), + previousNode.Pos(), + previousNode.Pos(), + previousNode.Pos(), MessageAddWhitespace, ) } @@ -116,6 +114,8 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { // * Allow idents that is used first in the block // * OR - if configured - anywhere in the block. } else { + // If we have a statement above and it's not an assignment or + // declaration we unconditionally add an error. w.addError( cursor.Stmt().Pos(), cursor.Stmt().Pos(), @@ -126,13 +126,12 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { } if w.Config.Errcheck && n == 0 && len(previousIdents) > 0 { - intersects := identIntersection(currentIdents, previousIdents) - if slices.ContainsFunc(intersects, func(ident *ast.Ident) bool { + if slices.ContainsFunc(previousIdents, func(ident *ast.Ident) bool { return w.implementsErr(ident) }) { w.addError( stmt.Pos(), - cursor.Stmt().End(), + previousNode.End(), stmt.Pos(), MessageRemoveWhitespace, ) @@ -151,12 +150,14 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { // // err := fn() // if err != nil {} + cursor.Previous() + if w.numberOfStatementsAbove(cursor) > 0 { w.addError( stmt.Pos(), - cursor.Stmt().Pos(), - cursor.Stmt().Pos(), - MessageRemoveWhitespace, + previousNode.Pos(), + previousNode.Pos(), + MessageAddWhitespace, ) } } @@ -183,7 +184,7 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { func (w *WSL) CheckBlock(block *ast.BlockStmt) { cursor := NewCursor(-1, block.List) for cursor.Next() { - fmt.Printf("%d: %T\n", cursor.currentIdx, cursor.Stmt()) + // fmt.Printf("%d: %T\n", cursor.currentIdx, cursor.Stmt()) w.CheckStmt(cursor.Stmt(), cursor) } } diff --git a/wsl_test.go b/wsl_test.go index 01d6815..958ff82 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -1,6 +1,7 @@ package wsl import ( + "path/filepath" "testing" "golang.org/x/tools/go/analysis/analysistest" @@ -8,14 +9,38 @@ import ( func TestDefaultConfig(t *testing.T) { testdata := analysistest.TestData() - analyzer := NewAnalyzer(nil) + analyzer := NewAnalyzer(&Configuration{}) analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "default_config") } +func TestWithConfig(t *testing.T) { + testdata := analysistest.TestData() + + for _, tc := range []struct { + subdir string + configFn func(*Configuration) + }{ + { + subdir: "if_errcheck", + configFn: func(config *Configuration) { + config.Errcheck = true + }, + }, + } { + t.Run(tc.subdir, func(t *testing.T) { + config := &Configuration{} + tc.configFn(config) + + analyzer := NewAnalyzer(config) + analysistest.RunWithSuggestedFixes(t, testdata, analyzer, filepath.Join("with_config", tc.subdir)) + }) + } +} + func TestWIP(t *testing.T) { testdata := analysistest.TestData() - analyzer := NewAnalyzer(nil) + analyzer := NewAnalyzer(&Configuration{}) analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "wip") } From 50ef04c9f0051143e8287df4340983a7dae9a50d Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 25 Jun 2024 23:40:41 +0200 Subject: [PATCH 012/112] Add check for return --- cursor.go | 8 ++++ testdata/src/default_config/return.go | 40 +++++++++++++++++ testdata/src/default_config/return.go.golden | 46 ++++++++++++++++++++ testdata/src/wip/wip.go | 2 - testdata/src/wip/wip.go.golden | 2 - wsl.go | 23 ++++++++++ 6 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 testdata/src/default_config/return.go create mode 100644 testdata/src/default_config/return.go.golden diff --git a/cursor.go b/cursor.go index dec379a..4647a7c 100644 --- a/cursor.go +++ b/cursor.go @@ -63,3 +63,11 @@ func (c *Cursor) Save() func() { c.currentIdx = idx } } + +func (c *Cursor) Len() int { + return len(c.statements) +} + +func (c *Cursor) Nth(n int) ast.Stmt { + return c.statements[n] +} diff --git a/testdata/src/default_config/return.go b/testdata/src/default_config/return.go new file mode 100644 index 0000000..9fac01f --- /dev/null +++ b/testdata/src/default_config/return.go @@ -0,0 +1,40 @@ +package testpkg + +func fn1() int { + return 1 +} + +func fn2() int { + _ = 1 + return 1 +} + +func fn3() int { + _ = 1 + _ = 2 + return 1 // want "missing whitespace decreases readability" +} + +func fn4() int { + if true { + _ = 1 + } + return 1 // want "missing whitespace decreases readability" +} + +func fn5() int { + if true { + _ = 1 + _ = 2 + return 1 // want "missing whitespace decreases readability" + } + return 1 // want "missing whitespace decreases readability" +} + +func fn6() func() { + _ = 1 + _ = 2 + return func() { // want "missing whitespace decreases readability" + _ = 1 + } +} diff --git a/testdata/src/default_config/return.go.golden b/testdata/src/default_config/return.go.golden new file mode 100644 index 0000000..e125c91 --- /dev/null +++ b/testdata/src/default_config/return.go.golden @@ -0,0 +1,46 @@ +package testpkg + +func fn1() int { + return 1 +} + +func fn2() int { + _ = 1 + return 1 +} + +func fn3() int { + _ = 1 + _ = 2 + + return 1 // want "missing whitespace decreases readability" +} + +func fn4() int { + if true { + _ = 1 + } + + return 1 // want "missing whitespace decreases readability" +} + +func fn5() int { + if true { + _ = 1 + _ = 2 + + return 1 // want "missing whitespace decreases readability" + } + + return 1 // want "missing whitespace decreases readability" +} + +func fn6() func() { + _ = 1 + _ = 2 + + return func() { // want "missing whitespace decreases readability" + _ = 1 + } +} + diff --git a/testdata/src/wip/wip.go b/testdata/src/wip/wip.go index e9ad075..a21e3cf 100644 --- a/testdata/src/wip/wip.go +++ b/testdata/src/wip/wip.go @@ -1,3 +1 @@ package testpkg - -func fn1() {} diff --git a/testdata/src/wip/wip.go.golden b/testdata/src/wip/wip.go.golden index e9ad075..a21e3cf 100644 --- a/testdata/src/wip/wip.go.golden +++ b/testdata/src/wip/wip.go.golden @@ -1,3 +1 @@ package testpkg - -func fn1() {} diff --git a/wsl.go b/wsl.go index 4d43b5f..ee4688b 100644 --- a/wsl.go +++ b/wsl.go @@ -189,6 +189,28 @@ func (w *WSL) CheckBlock(block *ast.BlockStmt) { } } +func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { + // There's only a return statement. + noStmts := cursor.Len() + if noStmts <= 1 { + return + } + + // If the distance between the first statement and the return statement is + // less than 3 LOC we're allowed to cuddle. + firstStmts := cursor.Nth(0) + if w.lineFor(stmt.End())-w.lineFor(firstStmts.Pos()) < 2 { + return + } + + w.addError( + stmt.Pos(), + stmt.Pos(), + stmt.Pos(), + MessageAddWhitespace, + ) +} + func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { //nolint:gocritic // This is not commented out code, it's examples switch s := stmt.(type) { @@ -205,6 +227,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { case *ast.TypeSwitchStmt: // return a case *ast.ReturnStmt: + w.CheckReturn(s, cursor) // continue / break case *ast.BranchStmt: // var a From d8f161ef9838cb24b523d2cdfc34f1e0a84ebcf1 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 20 Sep 2024 22:31:24 +0200 Subject: [PATCH 013/112] Add test for else/else-if --- testdata/src/default_config/leading_whitespace.go | 12 ++++++++++++ .../src/default_config/leading_whitespace.go.golden | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/testdata/src/default_config/leading_whitespace.go b/testdata/src/default_config/leading_whitespace.go index ce0a069..b0931b7 100644 --- a/testdata/src/default_config/leading_whitespace.go +++ b/testdata/src/default_config/leading_whitespace.go @@ -34,3 +34,15 @@ func fn6() { _ = 1 } } + +func fn7() { + if true { + _ = 1 + } else if true { // want "unnecessary whitespace decreases readability" + + _ = 1 + } else { // want "unnecessary whitespace decreases readability" + + _ = 1 + } +} diff --git a/testdata/src/default_config/leading_whitespace.go.golden b/testdata/src/default_config/leading_whitespace.go.golden index f973c64..ba99716 100644 --- a/testdata/src/default_config/leading_whitespace.go.golden +++ b/testdata/src/default_config/leading_whitespace.go.golden @@ -30,3 +30,13 @@ func fn6() { _ = 1 } } + +func fn7() { + if true { + _ = 1 + } else if true { // want "unnecessary whitespace decreases readability" + _ = 1 + } else { // want "unnecessary whitespace decreases readability" + _ = 1 + } +} From 1471bab3e4c70130b259aeb628383795892a6ba2 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 26 Sep 2024 11:31:14 +0200 Subject: [PATCH 014/112] For, range, switch --- testdata/src/default_config/for.go | 26 +++++++ testdata/src/default_config/for.go.golden | 30 +++++++++ testdata/src/default_config/range.go | 26 +++++++ testdata/src/default_config/range.go.golden | 30 +++++++++ testdata/src/default_config/switch.go | 35 ++++++++++ testdata/src/default_config/switch.go.golden | 39 +++++++++++ wsl.go | 71 ++++++++++++++++---- 7 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 testdata/src/default_config/for.go create mode 100644 testdata/src/default_config/for.go.golden create mode 100644 testdata/src/default_config/range.go create mode 100644 testdata/src/default_config/range.go.golden create mode 100644 testdata/src/default_config/switch.go create mode 100644 testdata/src/default_config/switch.go.golden diff --git a/testdata/src/default_config/for.go b/testdata/src/default_config/for.go new file mode 100644 index 0000000..f439c6a --- /dev/null +++ b/testdata/src/default_config/for.go @@ -0,0 +1,26 @@ +package testpkg + +func fn1() { + one := 1 + two := 2 // want "missing whitespace decreases readability" + for i := 0; i < two; i++ { + panic(err) + } +} + +func fn2() { + two := 2 + one := 1 + for i := 0; i < two; i++ { // want "missing whitespace decreases readability" + panic(err) + } +} + +func fn3() { + for i := 0; i < 1; i++ { + panic(err) + } + for i := 0; i < 1; i++ { // want "missing whitespace decreases readability" + panic(err) + } +} diff --git a/testdata/src/default_config/for.go.golden b/testdata/src/default_config/for.go.golden new file mode 100644 index 0000000..193f7b0 --- /dev/null +++ b/testdata/src/default_config/for.go.golden @@ -0,0 +1,30 @@ +package testpkg + +func fn1() { + one := 1 + + two := 2 // want "missing whitespace decreases readability" + for i := 0; i < two; i++ { + panic(err) + } +} + +func fn2() { + two := 2 + one := 1 + + for i := 0; i < two; i++ { // want "missing whitespace decreases readability" + panic(err) + } +} + +func fn3() { + for i := 0; i < 1; i++ { + panic(err) + } + + for i := 0; i < 1; i++ { // want "missing whitespace decreases readability" + panic(err) + } +} + diff --git a/testdata/src/default_config/range.go b/testdata/src/default_config/range.go new file mode 100644 index 0000000..d139cc1 --- /dev/null +++ b/testdata/src/default_config/range.go @@ -0,0 +1,26 @@ +package testpkg + +func fn1() { + one := []int{} + two := []int{} // want "missing whitespace decreases readability" + for range two { + panic(err) + } +} + +func fn2() { + two := []int{} + one := []int{} + for range two { // want "missing whitespace decreases readability" + panic(err) + } +} + +func fn3() { + for range make([]int{}, 0) { + panic(foo) + } + for range make([]int{}, 0) { // want "missing whitespace decreases readability" + panic(bar) + } +} diff --git a/testdata/src/default_config/range.go.golden b/testdata/src/default_config/range.go.golden new file mode 100644 index 0000000..7ee405c --- /dev/null +++ b/testdata/src/default_config/range.go.golden @@ -0,0 +1,30 @@ +package testpkg + +func fn1() { + one := []int{} + + two := []int{} // want "missing whitespace decreases readability" + for range two { + panic(err) + } +} + +func fn2() { + two := []int{} + one := []int{} + + for range two { // want "missing whitespace decreases readability" + panic(err) + } +} + +func fn3() { + for range make([]int{}, 0) { + panic(foo) + } + + for range make([]int{}, 0) { // want "missing whitespace decreases readability" + panic(bar) + } +} + diff --git a/testdata/src/default_config/switch.go b/testdata/src/default_config/switch.go new file mode 100644 index 0000000..6406a5f --- /dev/null +++ b/testdata/src/default_config/switch.go @@ -0,0 +1,35 @@ +package testpkg + +func fn1() { + one := 1 + two := 2 // want "missing whitespace decreases readability" + switch two { + case 1: + case 2: + case 3: + panic(err) + } +} + +func fn2() { + two := 2 + one := 1 + switch two { // want "missing whitespace decreases readability" + case 1: + case 2: + case 3: + panic(err) + } +} + +func fn3() { + switch true { + case true: + case false: + } + switch true { // want "missing whitespace decreases readability" + case true: + case false: + panic(err) + } +} diff --git a/testdata/src/default_config/switch.go.golden b/testdata/src/default_config/switch.go.golden new file mode 100644 index 0000000..126d6a3 --- /dev/null +++ b/testdata/src/default_config/switch.go.golden @@ -0,0 +1,39 @@ +package testpkg + +func fn1() { + one := 1 + + two := 2 // want "missing whitespace decreases readability" + switch two { + case 1: + case 2: + case 3: + panic(err) + } +} + +func fn2() { + two := 2 + one := 1 + + switch two { // want "missing whitespace decreases readability" + case 1: + case 2: + case 3: + panic(err) + } +} + +func fn3() { + switch true { + case true: + case false: + } + + switch true { // want "missing whitespace decreases readability" + case true: + case false: + panic(err) + } +} + diff --git a/wsl.go b/wsl.go index ee4688b..c460be4 100644 --- a/wsl.go +++ b/wsl.go @@ -58,17 +58,9 @@ func (w *WSL) Run() { } } -func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { - if funcDecl.Body == nil { - return - } - - w.CheckUnnecessaryBlockLeadingNewline(funcDecl.Body) - w.CheckBlock(funcDecl.Body) -} - -func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { +func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor) { reset := cursor.Save() + defer reset() currentIdents := allIdents(cursor.Stmt()) previousIdents := []*ast.Ident{} @@ -125,6 +117,10 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { } } + if _, ok := stmt.(*ast.IfStmt); !ok { + return + } + if w.Config.Errcheck && n == 0 && len(previousIdents) > 0 { if slices.ContainsFunc(previousIdents, func(ident *ast.Ident) bool { return w.implementsErr(ident) @@ -162,9 +158,19 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { } } } +} + +func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { + if funcDecl.Body == nil { + return + } - reset() + w.CheckUnnecessaryBlockLeadingNewline(funcDecl.Body) + w.CheckBlock(funcDecl.Body) +} +func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { + w.CheckCuddling(stmt, cursor) w.CheckUnnecessaryBlockLeadingNewline(stmt.Body) // if @@ -181,6 +187,21 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { } } +func (w *WSL) CheckFor(stmt *ast.ForStmt, cursor *Cursor) { + w.CheckCuddling(stmt, cursor) + w.CheckBlock(stmt.Body) +} + +func (w *WSL) CheckRange(stmt *ast.RangeStmt, cursor *Cursor) { + w.CheckCuddling(stmt, cursor) + w.CheckBlock(stmt.Body) +} + +func (w *WSL) CheckSwitch(stmt *ast.SwitchStmt, cursor *Cursor) { + w.CheckCuddling(stmt, cursor) + w.CheckBlock(stmt.Body) +} + func (w *WSL) CheckBlock(block *ast.BlockStmt) { cursor := NewCursor(-1, block.List) for cursor.Next() { @@ -219,10 +240,13 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { w.CheckIf(s, cursor) // for {} / for a; b; c {} case *ast.ForStmt: + w.CheckFor(s, cursor) // for _, _ = range a {} case *ast.RangeStmt: + w.CheckRange(s, cursor) // switch {} // switch a {} case *ast.SwitchStmt: + w.CheckSwitch(s, cursor) // switch a.(type) {} case *ast.TypeSwitchStmt: // return a @@ -241,6 +265,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { // go func() {} case *ast.GoStmt: case *ast.ExprStmt: + case *ast.CaseClause: case *ast.BlockStmt: w.CheckBlock(s) default: @@ -370,13 +395,35 @@ func allIdents(node ast.Node) []*ast.Ident { for _, lhs := range n.Lhs { idents = append(idents, allIdents(lhs)...) } + + // TODO: This should be here right? + for _, rhs := range n.Rhs { + idents = append(idents, allIdents(rhs)...) + } case *ast.IfStmt: idents = append(idents, allIdents(n.Cond)...) // TODO: idents = append(idents, allIdents(n.Else)...) case *ast.BinaryExpr: idents = append(idents, allIdents(n.X)...) idents = append(idents, allIdents(n.Y)...) - case *ast.BasicLit: + case *ast.RangeStmt: + idents = append(idents, allIdents(n.X)...) + case *ast.ForStmt: + idents = append(idents, allIdents(n.Init)...) + idents = append(idents, allIdents(n.Cond)...) + idents = append(idents, allIdents(n.Post)...) + case *ast.SwitchStmt: + idents = append(idents, allIdents(n.Init)...) + idents = append(idents, allIdents(n.Tag)...) + case *ast.CallExpr: + for _, arg := range n.Args { + idents = append(idents, allIdents(arg)...) + } + case *ast.CompositeLit: + for _, elt := range n.Elts { + idents = append(idents, allIdents(elt)...) + } + case *ast.BasicLit, *ast.IncDecStmt: default: spew.Dump(node) fmt.Printf("%T\n", node) From bca7f802eeae036ae0a00bea5230eef48eba7396 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 25 Dec 2024 19:33:21 +0100 Subject: [PATCH 015/112] Type switch --- testdata/src/default_config/type_switch.go | 44 +++++++++++++++++ .../src/default_config/type_switch.go.golden | 46 +++++++++++++++++ wsl.go | 49 ++++++++++++++----- 3 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 testdata/src/default_config/type_switch.go create mode 100644 testdata/src/default_config/type_switch.go.golden diff --git a/testdata/src/default_config/type_switch.go b/testdata/src/default_config/type_switch.go new file mode 100644 index 0000000..ab068ce --- /dev/null +++ b/testdata/src/default_config/type_switch.go @@ -0,0 +1,44 @@ +package testpkg + +func fn1() { + var v any + + v = 1 + switch v.(type) { + case int: + case string: + } +} + +func fn2() { + var v any + v = 1 + + one := 1 + switch v.(type) { // want "missing whitespace decreases readability" + case int: + case string: + } +} + +func fn3(in any) { + if true { + return + } + + switch v := in.(type) { + case string: + return + default: + } +} + +func fn4(in any) { + one := getOne() + two := getTwo() // want "missing whitespace decreases readability" + switch two.(type) { + case string: + return + default: + } +} diff --git a/testdata/src/default_config/type_switch.go.golden b/testdata/src/default_config/type_switch.go.golden new file mode 100644 index 0000000..e3bcd9b --- /dev/null +++ b/testdata/src/default_config/type_switch.go.golden @@ -0,0 +1,46 @@ +package testpkg + +func fn1() { + var v any + + v = 1 + switch v.(type) { + case int: + case string: + } +} + +func fn2() { + var v any + v = 1 + + one := 1 + + switch v.(type) { // want "missing whitespace decreases readability" + case int: + case string: + } +} + +func fn3(in any) { + if true { + return + } + + switch v := in.(type) { + case string: + return + default: + } +} + +func fn4(in any) { + one := getOne() + + two := getTwo() // want "missing whitespace decreases readability" + switch two.(type) { + case string: + return + default: + } +} diff --git a/wsl.go b/wsl.go index c460be4..9d06ab3 100644 --- a/wsl.go +++ b/wsl.go @@ -92,19 +92,22 @@ func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor) { ) } - // Idents on the line above exist in the current condition so that - // should remain cuddled. - if len(intersects) > 0 { - w.addError( - previousNode.Pos(), - previousNode.Pos(), - previousNode.Pos(), - MessageAddWhitespace, - ) + // Just one statement above and we have intersection + if n > 1 { + // Idents on the line above exist in the current condition so that + // should remain cuddled. + if len(intersects) > 0 { + w.addError( + previousNode.Pos(), + previousNode.Pos(), + previousNode.Pos(), + MessageAddWhitespace, + ) + } + // TODO: Features: + // * Allow idents that is used first in the block + // * OR - if configured - anywhere in the block. } - // TODO: Features: - // * Allow idents that is used first in the block - // * OR - if configured - anywhere in the block. } else { // If we have a statement above and it's not an assignment or // declaration we unconditionally add an error. @@ -202,6 +205,11 @@ func (w *WSL) CheckSwitch(stmt *ast.SwitchStmt, cursor *Cursor) { w.CheckBlock(stmt.Body) } +func (w *WSL) CheckTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { + w.CheckCuddling(stmt, cursor) + w.CheckBlock(stmt.Body) +} + func (w *WSL) CheckBlock(block *ast.BlockStmt) { cursor := NewCursor(-1, block.List) for cursor.Next() { @@ -249,6 +257,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { w.CheckSwitch(s, cursor) // switch a.(type) {} case *ast.TypeSwitchStmt: + w.CheckTypeSwitch(s, cursor) // return a case *ast.ReturnStmt: w.CheckReturn(s, cursor) @@ -391,7 +400,18 @@ func allIdents(node ast.Node) []*ast.Ident { switch n := node.(type) { case *ast.Ident: return []*ast.Ident{n} + case *ast.ExprStmt: + idents = append(idents, allIdents(n.X)...) case *ast.AssignStmt: + // TODO: For TypeSwitchStatements, this can be a false positive by + // allowing shadowing and "tricking" usage; + // var v any + + // notV := 1 + // switch notV := v.(type) {} + // + // This would trick wsl to see `notV` used in both type switch and on + // line above - faulty(?) for _, lhs := range n.Lhs { idents = append(idents, allIdents(lhs)...) } @@ -415,6 +435,11 @@ func allIdents(node ast.Node) []*ast.Ident { case *ast.SwitchStmt: idents = append(idents, allIdents(n.Init)...) idents = append(idents, allIdents(n.Tag)...) + case *ast.TypeSwitchStmt: + idents = append(idents, allIdents(n.Init)...) + idents = append(idents, allIdents(n.Assign)...) + case *ast.TypeAssertExpr: + idents = append(idents, allIdents(n.X)...) case *ast.CallExpr: for _, arg := range n.Args { idents = append(idents, allIdents(arg)...) From 618fed2aa3fb9ae6d76a3bb89356c0fc0a1e84e0 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 25 Dec 2024 20:02:12 +0100 Subject: [PATCH 016/112] Branch --- testdata/src/default_config/branch.go | 59 +++++++++++++++++++ testdata/src/default_config/branch.go.golden | 61 ++++++++++++++++++++ wsl.go | 21 ++++++- 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 testdata/src/default_config/branch.go create mode 100644 testdata/src/default_config/branch.go.golden diff --git a/testdata/src/default_config/branch.go b/testdata/src/default_config/branch.go new file mode 100644 index 0000000..ec53c9b --- /dev/null +++ b/testdata/src/default_config/branch.go @@ -0,0 +1,59 @@ +package testpkg + +import "fmt" + +func fn1() { + for i := range 10 { + if true { + fmt.Println("") + + continue + } + + if true { + fmt.Println("") + continue + } + + if true { + fmt.Println("") + break + } + + if false { + fmt.Println("") + fmt.Println("") + fmt.Println("") + + continue + } + + if false { + fmt.Println("") + fmt.Println("") + fmt.Println("") + + break + } + } +} + +func fn2() { + for i := range 10 { + if true { + fmt.Println("") + fmt.Println("") + fmt.Println("") + continue // want "missing whitespace decreases readability" + } + + if true { + fmt.Println("") + fmt.Println("") + fmt.Println("") + fmt.Println("") + fmt.Println("") + break // want "missing whitespace decreases readability" + } + } +} diff --git a/testdata/src/default_config/branch.go.golden b/testdata/src/default_config/branch.go.golden new file mode 100644 index 0000000..50d3171 --- /dev/null +++ b/testdata/src/default_config/branch.go.golden @@ -0,0 +1,61 @@ +package testpkg + +import "fmt" + +func fn1() { + for i := range 10 { + if true { + fmt.Println("") + + continue + } + + if true { + fmt.Println("") + continue + } + + if true { + fmt.Println("") + break + } + + if false { + fmt.Println("") + fmt.Println("") + fmt.Println("") + + continue + } + + if false { + fmt.Println("") + fmt.Println("") + fmt.Println("") + + break + } + } +} + +func fn2() { + for i := range 10 { + if true { + fmt.Println("") + fmt.Println("") + fmt.Println("") + + continue // want "missing whitespace decreases readability" + } + + if true { + fmt.Println("") + fmt.Println("") + fmt.Println("") + fmt.Println("") + fmt.Println("") + + break // want "missing whitespace decreases readability" + } + } +} diff --git a/wsl.go b/wsl.go index 9d06ab3..b5c497c 100644 --- a/wsl.go +++ b/wsl.go @@ -210,6 +210,24 @@ func (w *WSL) CheckTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { w.CheckBlock(stmt.Body) } +func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { + if w.numberOfStatementsAbove(cursor) == 0 { + return + } + + lastStmtInBlock := cursor.statements[len(cursor.statements)-1] + if stmt == lastStmtInBlock && len(cursor.statements) <= 2 { + return + } + + w.addError( + stmt.Pos(), + stmt.Pos(), + stmt.Pos(), + MessageAddWhitespace, + ) +} + func (w *WSL) CheckBlock(block *ast.BlockStmt) { cursor := NewCursor(-1, block.List) for cursor.Next() { @@ -263,6 +281,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { w.CheckReturn(s, cursor) // continue / break case *ast.BranchStmt: + w.CheckBranch(s, cursor) // var a case *ast.DeclStmt: // a := a @@ -448,7 +467,7 @@ func allIdents(node ast.Node) []*ast.Ident { for _, elt := range n.Elts { idents = append(idents, allIdents(elt)...) } - case *ast.BasicLit, *ast.IncDecStmt: + case *ast.BasicLit, *ast.IncDecStmt, *ast.BranchStmt: default: spew.Dump(node) fmt.Printf("%T\n", node) From f1f32603cd1b111578e51854da2d2d94054025b7 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 25 Dec 2024 20:23:10 +0100 Subject: [PATCH 017/112] Decl, check types config --- testdata/src/default_config/decl.go | 9 +++++ testdata/src/default_config/decl.go.golden | 11 ++++++ .../no_check_decl/no_check_decl.go | 8 ++++ .../no_check_decl/no_check_decl.go.golden | 9 +++++ wsl.go | 38 +++++++++++++++++++ wsl_test.go | 10 ++++- 6 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 testdata/src/default_config/decl.go create mode 100644 testdata/src/default_config/decl.go.golden create mode 100644 testdata/src/with_config/no_check_decl/no_check_decl.go create mode 100644 testdata/src/with_config/no_check_decl/no_check_decl.go.golden diff --git a/testdata/src/default_config/decl.go b/testdata/src/default_config/decl.go new file mode 100644 index 0000000..6166576 --- /dev/null +++ b/testdata/src/default_config/decl.go @@ -0,0 +1,9 @@ +package testpkg + +func fn1() { + var a = 1 + var b = 2 // want "missing whitespace decreases readability" + + const c = 3 + const d = 4 // want "missing whitespace decreases readability" +} diff --git a/testdata/src/default_config/decl.go.golden b/testdata/src/default_config/decl.go.golden new file mode 100644 index 0000000..a244df7 --- /dev/null +++ b/testdata/src/default_config/decl.go.golden @@ -0,0 +1,11 @@ +package testpkg + +func fn1() { + var a = 1 + + var b = 2 // want "missing whitespace decreases readability" + + const c = 3 + + const d = 4 // want "missing whitespace decreases readability" +} diff --git a/testdata/src/with_config/no_check_decl/no_check_decl.go b/testdata/src/with_config/no_check_decl/no_check_decl.go new file mode 100644 index 0000000..5a635e5 --- /dev/null +++ b/testdata/src/with_config/no_check_decl/no_check_decl.go @@ -0,0 +1,8 @@ +package testpkg + +func fn1() { + var a = 1 + var b = 2 + const c = 3 + const d = 4 +} diff --git a/testdata/src/with_config/no_check_decl/no_check_decl.go.golden b/testdata/src/with_config/no_check_decl/no_check_decl.go.golden new file mode 100644 index 0000000..c682dc2 --- /dev/null +++ b/testdata/src/with_config/no_check_decl/no_check_decl.go.golden @@ -0,0 +1,9 @@ +package testpkg + +func fn1() { + var a = 1 + var b = 2 + const c = 3 + const d = 4 +} + diff --git a/wsl.go b/wsl.go index b5c497c..f25bda8 100644 --- a/wsl.go +++ b/wsl.go @@ -16,9 +16,24 @@ const ( MessageRemoveWhitespace = "unnecessary whitespace decreases readability" ) +type CheckType int + +const ( + CheckTypeDecl CheckType = iota +) + type Configuration struct { // Require no newline between error assignment and error check. Errcheck bool + Checks map[CheckType]struct{} +} + +func NewConfig() *Configuration { + return &Configuration{ + Checks: map[CheckType]struct{}{ + CheckTypeDecl: {}, + }, + } } type FixRange struct { @@ -228,6 +243,18 @@ func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { ) } +func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { + if _, ok := w.Config.Checks[CheckTypeDecl]; !ok { + return + } + + if w.numberOfStatementsAbove(cursor) == 0 { + return + } + + w.CheckCuddling(stmt, cursor) +} + func (w *WSL) CheckBlock(block *ast.BlockStmt) { cursor := NewCursor(-1, block.List) for cursor.Next() { @@ -284,6 +311,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { w.CheckBranch(s, cursor) // var a case *ast.DeclStmt: + w.CheckDecl(s, cursor) // a := a case *ast.AssignStmt: // a++ / a-- @@ -421,6 +449,16 @@ func allIdents(node ast.Node) []*ast.Ident { return []*ast.Ident{n} case *ast.ExprStmt: idents = append(idents, allIdents(n.X)...) + case *ast.DeclStmt: + idents = append(idents, allIdents(n.Decl)...) + case *ast.GenDecl: + for _, spec := range n.Specs { + idents = append(idents, allIdents(spec)...) + } + case *ast.ValueSpec: + for _, name := range n.Names { + idents = append(idents, allIdents(name)...) + } case *ast.AssignStmt: // TODO: For TypeSwitchStatements, this can be a false positive by // allowing shadowing and "tricking" usage; diff --git a/wsl_test.go b/wsl_test.go index 958ff82..0ca15d7 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -9,7 +9,7 @@ import ( func TestDefaultConfig(t *testing.T) { testdata := analysistest.TestData() - analyzer := NewAnalyzer(&Configuration{}) + analyzer := NewAnalyzer(NewConfig()) analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "default_config") } @@ -27,6 +27,12 @@ func TestWithConfig(t *testing.T) { config.Errcheck = true }, }, + { + subdir: "no_check_decl", + configFn: func(config *Configuration) { + config.Errcheck = true + }, + }, } { t.Run(tc.subdir, func(t *testing.T) { config := &Configuration{} @@ -40,7 +46,7 @@ func TestWithConfig(t *testing.T) { func TestWIP(t *testing.T) { testdata := analysistest.TestData() - analyzer := NewAnalyzer(&Configuration{}) + analyzer := NewAnalyzer(NewConfig()) analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "wip") } From 2d5989fdfbebe4b5459d426411a4a443888fa46d Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 25 Dec 2024 20:41:48 +0100 Subject: [PATCH 018/112] More exploration with CheckType --- wsl.go | 34 +++++++++++++++++++++++++++++++--- wsl_test.go | 4 ++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/wsl.go b/wsl.go index f25bda8..25824ed 100644 --- a/wsl.go +++ b/wsl.go @@ -16,10 +16,17 @@ const ( MessageRemoveWhitespace = "unnecessary whitespace decreases readability" ) +// CheckType is a type that represents a checker to run. type CheckType int +// Each checker is represented by a CheckType that is used to enable or disable +// the check. const ( - CheckTypeDecl CheckType = iota + CheckDecl CheckType = iota + CheckBreak + CheckContinue + CheckIf + CheckLeadingWhitespace ) type Configuration struct { @@ -30,8 +37,13 @@ type Configuration struct { func NewConfig() *Configuration { return &Configuration{ + Errcheck: false, Checks: map[CheckType]struct{}{ - CheckTypeDecl: {}, + CheckBreak: {}, + CheckContinue: {}, + CheckDecl: {}, + CheckIf: {}, + CheckLeadingWhitespace: {}, }, } } @@ -188,6 +200,10 @@ func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { } func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { + if _, ok := w.Config.Checks[CheckIf]; !ok { + return + } + w.CheckCuddling(stmt, cursor) w.CheckUnnecessaryBlockLeadingNewline(stmt.Body) @@ -226,6 +242,14 @@ func (w *WSL) CheckTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { } func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { + if _, ok := w.Config.Checks[CheckBreak]; !ok && stmt.Tok == token.BREAK { + return + } + + if _, ok := w.Config.Checks[CheckContinue]; !ok && stmt.Tok == token.CONTINUE { + return + } + if w.numberOfStatementsAbove(cursor) == 0 { return } @@ -244,7 +268,7 @@ func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { } func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { - if _, ok := w.Config.Checks[CheckTypeDecl]; !ok { + if _, ok := w.Config.Checks[CheckDecl]; !ok { return } @@ -352,6 +376,10 @@ func (w *WSL) numberOfStatementsAbove(cursor *Cursor) int { } func (w *WSL) CheckUnnecessaryBlockLeadingNewline(body *ast.BlockStmt) { + if _, ok := w.Config.Checks[CheckLeadingWhitespace]; !ok { + return + } + // No statements in the block, let's leave it as is. if len(body.List) == 0 { return diff --git a/wsl_test.go b/wsl_test.go index 0ca15d7..9ddae7d 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -30,12 +30,12 @@ func TestWithConfig(t *testing.T) { { subdir: "no_check_decl", configFn: func(config *Configuration) { - config.Errcheck = true + delete(config.Checks, CheckDecl) }, }, } { t.Run(tc.subdir, func(t *testing.T) { - config := &Configuration{} + config := NewConfig() tc.configFn(config) analyzer := NewAnalyzer(config) From bd5c17bd36a5fddcf56d26331be4c125ebe2ccdd Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 31 Dec 2024 10:30:21 +0100 Subject: [PATCH 019/112] Improve leading whitespace check --- .../src/default_config/leading_whitespace.go | 53 +++++++++++++++++++ .../leading_whitespace.go.golden | 43 +++++++++++++++ wsl.go | 53 +++++++++++++++++-- 3 files changed, 144 insertions(+), 5 deletions(-) diff --git a/testdata/src/default_config/leading_whitespace.go b/testdata/src/default_config/leading_whitespace.go index b0931b7..fbd549d 100644 --- a/testdata/src/default_config/leading_whitespace.go +++ b/testdata/src/default_config/leading_whitespace.go @@ -28,6 +28,13 @@ func fn5() { fmt.Println("Hello, World") } +func fn51() { + // Comment with + + // a newline between + _ = 1 +} + func fn6() { if true { // want "unnecessary whitespace decreases readability" @@ -46,3 +53,49 @@ func fn7() { _ = 1 } } + +func fn8(a string, b any, s []string) { // want "unnecessary whitespace decreases readability" + + if true { // want "unnecessary whitespace decreases readability" + + _ = 1 + } else if true { // want "unnecessary whitespace decreases readability" + + _ = 1 + } + + for i := 0; i < 1; i++ { // want "unnecessary whitespace decreases readability" + + _ = 1 + } + + for n := range 10 { // want "unnecessary whitespace decreases readability" + + _ = 1 + } + + for range s { // want "unnecessary whitespace decreases readability" + + _ = 1 + } + + switch a { // want "unnecessary whitespace decreases readability" + + case "a": + } + + switch b.(type) { // want "unnecessary whitespace decreases readability" + + case int: + } + + f := func() { // want "unnecessary whitespace decreases readability" + + _ = 1 + } + + f2 := Call(func() { // want "unnecessary whitespace decreases readability" + + _ = 1 + }) +} diff --git a/testdata/src/default_config/leading_whitespace.go.golden b/testdata/src/default_config/leading_whitespace.go.golden index ba99716..6d6621b 100644 --- a/testdata/src/default_config/leading_whitespace.go.golden +++ b/testdata/src/default_config/leading_whitespace.go.golden @@ -25,6 +25,13 @@ func fn5() { fmt.Println("Hello, World") } +func fn51() { + // Comment with + + // a newline between + _ = 1 +} + func fn6() { if true { // want "unnecessary whitespace decreases readability" _ = 1 @@ -40,3 +47,39 @@ func fn7() { _ = 1 } } + +func fn8(a string, b any, s []string) { // want "unnecessary whitespace decreases readability" + if true { // want "unnecessary whitespace decreases readability" + _ = 1 + } else if true { // want "unnecessary whitespace decreases readability" + _ = 1 + } + + for i := 0; i < 1; i++ { // want "unnecessary whitespace decreases readability" + _ = 1 + } + + for n := range 10 { // want "unnecessary whitespace decreases readability" + _ = 1 + } + + for range s { // want "unnecessary whitespace decreases readability" + _ = 1 + } + + switch a { // want "unnecessary whitespace decreases readability" + case "a": + } + + switch b.(type) { // want "unnecessary whitespace decreases readability" + case int: + } + + f := func() { // want "unnecessary whitespace decreases readability" + _ = 1 + } + + f2 := Call(func() { // want "unnecessary whitespace decreases readability" + _ = 1 + }) +} diff --git a/wsl.go b/wsl.go index 25824ed..aadbb62 100644 --- a/wsl.go +++ b/wsl.go @@ -195,7 +195,6 @@ func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { return } - w.CheckUnnecessaryBlockLeadingNewline(funcDecl.Body) w.CheckBlock(funcDecl.Body) } @@ -205,7 +204,6 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { } w.CheckCuddling(stmt, cursor) - w.CheckUnnecessaryBlockLeadingNewline(stmt.Body) // if w.CheckBlock(stmt.Body) @@ -216,7 +214,6 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { w.CheckIf(v, cursor) // else case *ast.BlockStmt: - w.CheckUnnecessaryBlockLeadingNewline(v) w.CheckBlock(v) } } @@ -280,6 +277,8 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { } func (w *WSL) CheckBlock(block *ast.BlockStmt) { + w.CheckUnnecessaryBlockLeadingNewline(block) + cursor := NewCursor(-1, block.List) for cursor.Next() { // fmt.Printf("%d: %T\n", cursor.currentIdx, cursor.Stmt()) @@ -309,6 +308,33 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { ) } +func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { + defer cursor.Save()() + + var previousNode ast.Node + + if cursor.Previous() { + previousNode = cursor.Stmt() + cursor.Next() // Move forward again + } + + _, prevIsAssign := previousNode.(*ast.AssignStmt) + _, prevIsDecl := previousNode.(*ast.DeclStmt) + + if w.numberOfStatementsAbove(cursor) > 0 && previousNode != nil && !prevIsAssign && !prevIsDecl { + w.addError( + stmt.Pos(), + stmt.Pos(), + stmt.Pos(), + MessageAddWhitespace, + ) + } + + for _, expr := range stmt.Rhs { + w.CheckExpr(expr, cursor) + } +} + func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { //nolint:gocritic // This is not commented out code, it's examples switch s := stmt.(type) { @@ -338,6 +364,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { w.CheckDecl(s, cursor) // a := a case *ast.AssignStmt: + w.CheckAssign(s, cursor) // a++ / a-- case *ast.IncDecStmt: // defer func() {} @@ -349,7 +376,23 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { case *ast.BlockStmt: w.CheckBlock(s) default: - fmt.Printf("Not implemented: %T\n", s) + fmt.Printf("Not implemented stmt: %T\n", s) + } +} + +func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) { + switch s := expr.(type) { + // func() {} + case *ast.FuncLit: + w.CheckBlock(s.Body) + // Call(args...) + case *ast.CallExpr: + for _, e := range s.Args { + w.CheckExpr(e, cursor) + } + case *ast.BasicLit, *ast.CompositeLit: + default: + fmt.Printf("Not implemented expr: %T\n", s) } } @@ -533,7 +576,7 @@ func allIdents(node ast.Node) []*ast.Ident { for _, elt := range n.Elts { idents = append(idents, allIdents(elt)...) } - case *ast.BasicLit, *ast.IncDecStmt, *ast.BranchStmt: + case *ast.BasicLit, *ast.FuncLit, *ast.IncDecStmt, *ast.BranchStmt: default: spew.Dump(node) fmt.Printf("%T\n", node) From 20afa0ed634fecc5b2e85c7bd880863e6a444c0e Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 1 Jan 2025 11:46:39 +0100 Subject: [PATCH 020/112] Add initial expression statement check --- testdata/src/default_config/expr.go | 27 +++++++++++++++++++ testdata/src/default_config/expr.go.golden | 30 ++++++++++++++++++++++ wsl.go | 28 ++++++++++++-------- 3 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 testdata/src/default_config/expr.go create mode 100644 testdata/src/default_config/expr.go.golden diff --git a/testdata/src/default_config/expr.go b/testdata/src/default_config/expr.go new file mode 100644 index 0000000..aad131b --- /dev/null +++ b/testdata/src/default_config/expr.go @@ -0,0 +1,27 @@ +package testpkg + +import "fmt" + +func fn() { + a := 1 + b := 2 + c := 3 + a = 4 + b = 3 +} + +func fn2() { + a := 1 + b := 2 + fmt.Println("") // want "missing whitespace decreases readability" + c := 3 // want "missing whitespace decreases readability" + d := 4 +} + +func fn3() { + a := 1 + b := 2 + fmt.Println(b) + c := 3 // want "missing whitespace decreases readability" + d := 4 +} diff --git a/testdata/src/default_config/expr.go.golden b/testdata/src/default_config/expr.go.golden new file mode 100644 index 0000000..8b6b0e0 --- /dev/null +++ b/testdata/src/default_config/expr.go.golden @@ -0,0 +1,30 @@ +package testpkg + +import "fmt" + +func fn() { + a := 1 + b := 2 + c := 3 + a = 4 + b = 3 +} + +func fn2() { + a := 1 + b := 2 + + fmt.Println("") // want "missing whitespace decreases readability" + + c := 3 // want "missing whitespace decreases readability" + d := 4 +} + +func fn3() { + a := 1 + b := 2 + fmt.Println(b) + + c := 3 // want "missing whitespace decreases readability" + d := 4 +} diff --git a/wsl.go b/wsl.go index aadbb62..212c9f2 100644 --- a/wsl.go +++ b/wsl.go @@ -85,7 +85,7 @@ func (w *WSL) Run() { } } -func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor) { +func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { reset := cursor.Save() defer reset() @@ -119,8 +119,10 @@ func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor) { ) } - // Just one statement above and we have intersection - if n > 1 { + // Check the maximum number of allowed statements above, and if not + // disabled (-1) check that the previous one intersects with the + // current one. + if maxAllowedStatements != -1 && n > maxAllowedStatements { // Idents on the line above exist in the current condition so that // should remain cuddled. if len(intersects) > 0 { @@ -203,7 +205,7 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { return } - w.CheckCuddling(stmt, cursor) + w.CheckCuddling(stmt, cursor, 1) // if w.CheckBlock(stmt.Body) @@ -219,25 +221,30 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { } func (w *WSL) CheckFor(stmt *ast.ForStmt, cursor *Cursor) { - w.CheckCuddling(stmt, cursor) + w.CheckCuddling(stmt, cursor, 1) w.CheckBlock(stmt.Body) } func (w *WSL) CheckRange(stmt *ast.RangeStmt, cursor *Cursor) { - w.CheckCuddling(stmt, cursor) + w.CheckCuddling(stmt, cursor, 1) w.CheckBlock(stmt.Body) } func (w *WSL) CheckSwitch(stmt *ast.SwitchStmt, cursor *Cursor) { - w.CheckCuddling(stmt, cursor) + w.CheckCuddling(stmt, cursor, 1) w.CheckBlock(stmt.Body) } func (w *WSL) CheckTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { - w.CheckCuddling(stmt, cursor) + w.CheckCuddling(stmt, cursor, 1) w.CheckBlock(stmt.Body) } +func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { + w.CheckCuddling(stmt, cursor, -1) + w.CheckExpr(stmt.X, cursor) +} + func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { if _, ok := w.Config.Checks[CheckBreak]; !ok && stmt.Tok == token.BREAK { return @@ -273,7 +280,7 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { return } - w.CheckCuddling(stmt, cursor) + w.CheckCuddling(stmt, cursor, 1) } func (w *WSL) CheckBlock(block *ast.BlockStmt) { @@ -372,6 +379,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { // go func() {} case *ast.GoStmt: case *ast.ExprStmt: + w.CheckExprStmt(s, cursor) case *ast.CaseClause: case *ast.BlockStmt: w.CheckBlock(s) @@ -390,7 +398,7 @@ func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) { for _, e := range s.Args { w.CheckExpr(e, cursor) } - case *ast.BasicLit, *ast.CompositeLit: + case *ast.BasicLit, *ast.CompositeLit, *ast.Ident: default: fmt.Printf("Not implemented expr: %T\n", s) } From b972ec114db9eebef5d5df7773072935488f0f5b Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 1 Jan 2025 16:33:21 +0100 Subject: [PATCH 021/112] Go stmt --- cursor.go | 11 ++++++ testdata/src/default_config/go.go | 45 +++++++++++++++++++++ testdata/src/default_config/go.go.golden | 50 ++++++++++++++++++++++++ wsl.go | 32 ++++++++++----- 4 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 testdata/src/default_config/go.go create mode 100644 testdata/src/default_config/go.go.golden diff --git a/cursor.go b/cursor.go index 4647a7c..6050396 100644 --- a/cursor.go +++ b/cursor.go @@ -39,6 +39,17 @@ func (c *Cursor) Previous() bool { return true } +func (c *Cursor) PreviousNode() ast.Node { + defer c.Save()() + + var previousNode ast.Node + if c.Previous() { + previousNode = c.Stmt() + } + + return previousNode +} + func (c *Cursor) PeekNext() bool { reset := c.Save() defer reset() diff --git a/testdata/src/default_config/go.go b/testdata/src/default_config/go.go new file mode 100644 index 0000000..ac5dc89 --- /dev/null +++ b/testdata/src/default_config/go.go @@ -0,0 +1,45 @@ +package testpkg + +import "fmt" + +func Go() { + fooFunc := func() {} + go fooFunc() + + barFunc := func() {} + go fooFunc() // want "missing whitespace decreases readability" + + go func() { + fmt.Println("hey") + }() + + cuddled := true + go func() { // want "missing whitespace decreases readability" + fmt.Println("hey") + }() + + argToGo := 1 + go takesArg(argToGo) + + notArgToGo := 1 + go takesArg(argToGo) // want "missing whitespace decreases readability" + + t1 := NewT() + t2 := NewT() + t3 := NewT() + + go t1() + go t2() + go t3() + + multiCuddle1 := NewT() + multiCuddle2 := NewT() // want "missing whitespace decreases readability" + go multiCuddle2() + + // TODO: Breaking change, this used to be on the first `go` stmt - now it's + // on the line that should have a blank line above. + t4 := NewT() + t5 := NewT() // want "missing whitespace decreases readability" + go t5() + go t4() +} diff --git a/testdata/src/default_config/go.go.golden b/testdata/src/default_config/go.go.golden new file mode 100644 index 0000000..1eff902 --- /dev/null +++ b/testdata/src/default_config/go.go.golden @@ -0,0 +1,50 @@ +package testpkg + +import "fmt" + +func Go() { + fooFunc := func() {} + go fooFunc() + + barFunc := func() {} + + go fooFunc() // want "missing whitespace decreases readability" + + go func() { + fmt.Println("hey") + }() + + cuddled := true + + go func() { // want "missing whitespace decreases readability" + fmt.Println("hey") + }() + + argToGo := 1 + go takesArg(argToGo) + + notArgToGo := 1 + + go takesArg(argToGo) // want "missing whitespace decreases readability" + + t1 := NewT() + t2 := NewT() + t3 := NewT() + + go t1() + go t2() + go t3() + + multiCuddle1 := NewT() + + multiCuddle2 := NewT() // want "missing whitespace decreases readability" + go multiCuddle2() + + // TODO: Breaking change, this used to be on the first `go` stmt - now it's + // on the line that should have a blank line above. + t4 := NewT() + + t5 := NewT() // want "missing whitespace decreases readability" + go t5() + go t4() +} diff --git a/wsl.go b/wsl.go index 212c9f2..d1464f3 100644 --- a/wsl.go +++ b/wsl.go @@ -245,6 +245,20 @@ func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { w.CheckExpr(stmt.X, cursor) } +func (w *WSL) CheckGo(stmt *ast.GoStmt, cursor *Cursor) { + previousNode := cursor.PreviousNode() + + // We can cuddle any amount `go` statements so only check cuddling if the + // previous one isn't a `go` call. + // We don't even have to check if it's actually cuddled or just the previous + // one because even if it's not but is a `go` statement it's valid. + if _, ok := previousNode.(*ast.GoStmt); !ok { + w.CheckCuddling(stmt, cursor, 1) + } + + w.CheckExpr(stmt.Call, cursor) +} + func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { if _, ok := w.Config.Checks[CheckBreak]; !ok && stmt.Tok == token.BREAK { return @@ -316,15 +330,7 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { } func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { - defer cursor.Save()() - - var previousNode ast.Node - - if cursor.Previous() { - previousNode = cursor.Stmt() - cursor.Next() // Move forward again - } - + previousNode := cursor.PreviousNode() _, prevIsAssign := previousNode.(*ast.AssignStmt) _, prevIsDecl := previousNode.(*ast.DeclStmt) @@ -378,6 +384,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { case *ast.DeferStmt: // go func() {} case *ast.GoStmt: + w.CheckGo(s, cursor) case *ast.ExprStmt: w.CheckExprStmt(s, cursor) case *ast.CaseClause: @@ -534,6 +541,8 @@ func allIdents(node ast.Node) []*ast.Ident { for _, spec := range n.Specs { idents = append(idents, allIdents(spec)...) } + case *ast.GoStmt: + idents = append(idents, allIdents(n.Call)...) case *ast.ValueSpec: for _, name := range n.Names { idents = append(idents, allIdents(name)...) @@ -552,7 +561,9 @@ func allIdents(node ast.Node) []*ast.Ident { idents = append(idents, allIdents(lhs)...) } - // TODO: This should be here right? + // This must be here to see if a variable is used on the RHS, e.g. + // a := 1 + // b = append(b, fmt.Sprintf("%s", a)) for _, rhs := range n.Rhs { idents = append(idents, allIdents(rhs)...) } @@ -577,6 +588,7 @@ func allIdents(node ast.Node) []*ast.Ident { case *ast.TypeAssertExpr: idents = append(idents, allIdents(n.X)...) case *ast.CallExpr: + idents = append(idents, allIdents(n.Fun)...) for _, arg := range n.Args { idents = append(idents, allIdents(arg)...) } From b749b981c4dea09b7a5697afe5320e495cc0928c Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 1 Jan 2025 17:12:06 +0100 Subject: [PATCH 022/112] Reorganize tests --- .../src/default_config/{ => branch}/branch.go | 4 +- .../{ => branch}/branch.go.golden | 4 +- .../src/default_config/{ => decl}/decl.go | 3 ++ .../default_config/{ => decl}/decl.go.golden | 3 ++ .../src/default_config/{ => expr}/expr.go | 14 +++++++ .../default_config/{ => expr}/expr.go.golden | 14 +++++++ testdata/src/default_config/for.go | 26 ------------- testdata/src/default_config/for.go.golden | 30 -------------- testdata/src/default_config/for/for.go | 26 +++++++++++++ testdata/src/default_config/for/for.go.golden | 29 ++++++++++++++ testdata/src/default_config/{ => go}/go.go | 24 +++++++++++- .../src/default_config/{ => go}/go.go.golden | 27 +++++++++++-- testdata/src/default_config/if.go | 36 ----------------- testdata/src/default_config/if.go.golden | 39 ------------------- testdata/src/default_config/if/if.go | 36 +++++++++++++++++ testdata/src/default_config/if/if.go.golden | 39 +++++++++++++++++++ testdata/src/default_config/range.go | 26 ------------- testdata/src/default_config/range.go.golden | 30 -------------- testdata/src/default_config/range/range.go | 26 +++++++++++++ .../src/default_config/range/range.go.golden | 29 ++++++++++++++ .../src/default_config/{ => return}/return.go | 0 .../{ => return}/return.go.golden | 0 .../src/default_config/{ => switch}/switch.go | 18 ++++----- .../{ => switch}/switch.go.golden | 18 ++++----- .../type_switch.go} | 27 ++++++++----- .../type_switch.go.golden} | 28 +++++++++---- .../{ => whitespace}/leading_whitespace.go | 11 +++++- .../leading_whitespace.go.golden | 11 +++++- testdata/src/wip/wip.go | 1 - testdata/src/wip/wip.go.golden | 1 - .../with_config/if_errcheck/if_errcheck.go | 11 ++++-- .../if_errcheck/if_errcheck.go.golden | 12 ++++-- .../no_check_decl/no_check_decl.go | 5 +++ .../no_check_decl/no_check_decl.go.golden | 5 +++ wsl.go | 18 +++++++-- wsl_test.go | 23 ++++++----- 36 files changed, 397 insertions(+), 257 deletions(-) rename testdata/src/default_config/{ => branch}/branch.go (94%) rename testdata/src/default_config/{ => branch}/branch.go.golden (94%) rename testdata/src/default_config/{ => decl}/decl.go (92%) rename testdata/src/default_config/{ => decl}/decl.go.golden (92%) rename testdata/src/default_config/{ => expr}/expr.go (82%) rename testdata/src/default_config/{ => expr}/expr.go.golden (82%) delete mode 100644 testdata/src/default_config/for.go delete mode 100644 testdata/src/default_config/for.go.golden create mode 100644 testdata/src/default_config/for/for.go create mode 100644 testdata/src/default_config/for/for.go.golden rename testdata/src/default_config/{ => go}/go.go (74%) rename testdata/src/default_config/{ => go}/go.go.golden (72%) delete mode 100644 testdata/src/default_config/if.go delete mode 100644 testdata/src/default_config/if.go.golden create mode 100644 testdata/src/default_config/if/if.go create mode 100644 testdata/src/default_config/if/if.go.golden delete mode 100644 testdata/src/default_config/range.go delete mode 100644 testdata/src/default_config/range.go.golden create mode 100644 testdata/src/default_config/range/range.go create mode 100644 testdata/src/default_config/range/range.go.golden rename testdata/src/default_config/{ => return}/return.go (100%) rename testdata/src/default_config/{ => return}/return.go.golden (100%) rename testdata/src/default_config/{ => switch}/switch.go (55%) rename testdata/src/default_config/{ => switch}/switch.go.golden (55%) rename testdata/src/default_config/{type_switch.go.golden => type_switch/type_switch.go} (52%) rename testdata/src/default_config/{type_switch.go => type_switch/type_switch.go.golden} (52%) rename testdata/src/default_config/{ => whitespace}/leading_whitespace.go (91%) rename testdata/src/default_config/{ => whitespace}/leading_whitespace.go.golden (91%) delete mode 100644 testdata/src/wip/wip.go delete mode 100644 testdata/src/wip/wip.go.golden diff --git a/testdata/src/default_config/branch.go b/testdata/src/default_config/branch/branch.go similarity index 94% rename from testdata/src/default_config/branch.go rename to testdata/src/default_config/branch/branch.go index ec53c9b..9608930 100644 --- a/testdata/src/default_config/branch.go +++ b/testdata/src/default_config/branch/branch.go @@ -3,7 +3,7 @@ package testpkg import "fmt" func fn1() { - for i := range 10 { + for range []int{} { if true { fmt.Println("") @@ -39,7 +39,7 @@ func fn1() { } func fn2() { - for i := range 10 { + for range []int{} { if true { fmt.Println("") fmt.Println("") diff --git a/testdata/src/default_config/branch.go.golden b/testdata/src/default_config/branch/branch.go.golden similarity index 94% rename from testdata/src/default_config/branch.go.golden rename to testdata/src/default_config/branch/branch.go.golden index 50d3171..21beba6 100644 --- a/testdata/src/default_config/branch.go.golden +++ b/testdata/src/default_config/branch/branch.go.golden @@ -3,7 +3,7 @@ package testpkg import "fmt" func fn1() { - for i := range 10 { + for range []int{} { if true { fmt.Println("") @@ -39,7 +39,7 @@ func fn1() { } func fn2() { - for i := range 10 { + for range []int{} { if true { fmt.Println("") fmt.Println("") diff --git a/testdata/src/default_config/decl.go b/testdata/src/default_config/decl/decl.go similarity index 92% rename from testdata/src/default_config/decl.go rename to testdata/src/default_config/decl/decl.go index 6166576..0402968 100644 --- a/testdata/src/default_config/decl.go +++ b/testdata/src/default_config/decl/decl.go @@ -6,4 +6,7 @@ func fn1() { const c = 3 const d = 4 // want "missing whitespace decreases readability" + + _ = a + _ = b } diff --git a/testdata/src/default_config/decl.go.golden b/testdata/src/default_config/decl/decl.go.golden similarity index 92% rename from testdata/src/default_config/decl.go.golden rename to testdata/src/default_config/decl/decl.go.golden index a244df7..a778706 100644 --- a/testdata/src/default_config/decl.go.golden +++ b/testdata/src/default_config/decl/decl.go.golden @@ -8,4 +8,7 @@ func fn1() { const c = 3 const d = 4 // want "missing whitespace decreases readability" + + _ = a + _ = b } diff --git a/testdata/src/default_config/expr.go b/testdata/src/default_config/expr/expr.go similarity index 82% rename from testdata/src/default_config/expr.go rename to testdata/src/default_config/expr/expr.go index aad131b..1a840ee 100644 --- a/testdata/src/default_config/expr.go +++ b/testdata/src/default_config/expr/expr.go @@ -8,6 +8,10 @@ func fn() { c := 3 a = 4 b = 3 + + _ = a + _ = b + _ = c } func fn2() { @@ -16,6 +20,11 @@ func fn2() { fmt.Println("") // want "missing whitespace decreases readability" c := 3 // want "missing whitespace decreases readability" d := 4 + + _ = a + _ = b + _ = c + _ = d } func fn3() { @@ -24,4 +33,9 @@ func fn3() { fmt.Println(b) c := 3 // want "missing whitespace decreases readability" d := 4 + + _ = a + _ = b + _ = c + _ = d } diff --git a/testdata/src/default_config/expr.go.golden b/testdata/src/default_config/expr/expr.go.golden similarity index 82% rename from testdata/src/default_config/expr.go.golden rename to testdata/src/default_config/expr/expr.go.golden index 8b6b0e0..ce27599 100644 --- a/testdata/src/default_config/expr.go.golden +++ b/testdata/src/default_config/expr/expr.go.golden @@ -8,6 +8,10 @@ func fn() { c := 3 a = 4 b = 3 + + _ = a + _ = b + _ = c } func fn2() { @@ -18,6 +22,11 @@ func fn2() { c := 3 // want "missing whitespace decreases readability" d := 4 + + _ = a + _ = b + _ = c + _ = d } func fn3() { @@ -27,4 +36,9 @@ func fn3() { c := 3 // want "missing whitespace decreases readability" d := 4 + + _ = a + _ = b + _ = c + _ = d } diff --git a/testdata/src/default_config/for.go b/testdata/src/default_config/for.go deleted file mode 100644 index f439c6a..0000000 --- a/testdata/src/default_config/for.go +++ /dev/null @@ -1,26 +0,0 @@ -package testpkg - -func fn1() { - one := 1 - two := 2 // want "missing whitespace decreases readability" - for i := 0; i < two; i++ { - panic(err) - } -} - -func fn2() { - two := 2 - one := 1 - for i := 0; i < two; i++ { // want "missing whitespace decreases readability" - panic(err) - } -} - -func fn3() { - for i := 0; i < 1; i++ { - panic(err) - } - for i := 0; i < 1; i++ { // want "missing whitespace decreases readability" - panic(err) - } -} diff --git a/testdata/src/default_config/for.go.golden b/testdata/src/default_config/for.go.golden deleted file mode 100644 index 193f7b0..0000000 --- a/testdata/src/default_config/for.go.golden +++ /dev/null @@ -1,30 +0,0 @@ -package testpkg - -func fn1() { - one := 1 - - two := 2 // want "missing whitespace decreases readability" - for i := 0; i < two; i++ { - panic(err) - } -} - -func fn2() { - two := 2 - one := 1 - - for i := 0; i < two; i++ { // want "missing whitespace decreases readability" - panic(err) - } -} - -func fn3() { - for i := 0; i < 1; i++ { - panic(err) - } - - for i := 0; i < 1; i++ { // want "missing whitespace decreases readability" - panic(err) - } -} - diff --git a/testdata/src/default_config/for/for.go b/testdata/src/default_config/for/for.go new file mode 100644 index 0000000..af824ca --- /dev/null +++ b/testdata/src/default_config/for/for.go @@ -0,0 +1,26 @@ +package testpkg + +func fn1() { + a := 1 + b := 2 // want "missing whitespace decreases readability" + for i := 0; i < b; i++ { + panic(a) + } +} + +func fn2() { + b := 2 + a := 1 + for i := 0; i < b; i++ { // want "missing whitespace decreases readability" + panic(a) + } +} + +func fn3() { + for i := 0; i < 1; i++ { + panic("") + } + for i := 0; i < 1; i++ { // want "missing whitespace decreases readability" + panic("") + } +} diff --git a/testdata/src/default_config/for/for.go.golden b/testdata/src/default_config/for/for.go.golden new file mode 100644 index 0000000..b10799e --- /dev/null +++ b/testdata/src/default_config/for/for.go.golden @@ -0,0 +1,29 @@ +package testpkg + +func fn1() { + a := 1 + + b := 2 // want "missing whitespace decreases readability" + for i := 0; i < b; i++ { + panic(a) + } +} + +func fn2() { + b := 2 + a := 1 + + for i := 0; i < b; i++ { // want "missing whitespace decreases readability" + panic(a) + } +} + +func fn3() { + for i := 0; i < 1; i++ { + panic("") + } + + for i := 0; i < 1; i++ { // want "missing whitespace decreases readability" + panic("") + } +} diff --git a/testdata/src/default_config/go.go b/testdata/src/default_config/go/go.go similarity index 74% rename from testdata/src/default_config/go.go rename to testdata/src/default_config/go/go.go index ac5dc89..db77ae7 100644 --- a/testdata/src/default_config/go.go +++ b/testdata/src/default_config/go/go.go @@ -2,6 +2,12 @@ package testpkg import "fmt" +func NewT() func() { + return func() {} +} + +func Fn(_ int) {} + func Go() { fooFunc := func() {} go fooFunc() @@ -9,6 +15,8 @@ func Go() { barFunc := func() {} go fooFunc() // want "missing whitespace decreases readability" + _ = barFunc + go func() { fmt.Println("hey") }() @@ -18,11 +26,15 @@ func Go() { fmt.Println("hey") }() + _ = cuddled + argToGo := 1 - go takesArg(argToGo) + go Fn(argToGo) notArgToGo := 1 - go takesArg(argToGo) // want "missing whitespace decreases readability" + go Fn(argToGo) // want "missing whitespace decreases readability" + + _ = notArgToGo t1 := NewT() t2 := NewT() @@ -42,4 +54,12 @@ func Go() { t5 := NewT() // want "missing whitespace decreases readability" go t5() go t4() + + _ = t1 + _ = t2 + _ = t3 + _ = t4 + _ = t5 + _ = multiCuddle1 + _ = multiCuddle2 } diff --git a/testdata/src/default_config/go.go.golden b/testdata/src/default_config/go/go.go.golden similarity index 72% rename from testdata/src/default_config/go.go.golden rename to testdata/src/default_config/go/go.go.golden index 1eff902..78b0c70 100644 --- a/testdata/src/default_config/go.go.golden +++ b/testdata/src/default_config/go/go.go.golden @@ -1,7 +1,14 @@ + package testpkg import "fmt" +func NewT() func() { + return func() {} +} + +func Fn(_ int) {} + func Go() { fooFunc := func() {} go fooFunc() @@ -10,6 +17,8 @@ func Go() { go fooFunc() // want "missing whitespace decreases readability" + _ = barFunc + go func() { fmt.Println("hey") }() @@ -20,12 +29,16 @@ func Go() { fmt.Println("hey") }() + _ = cuddled + argToGo := 1 - go takesArg(argToGo) + go Fn(argToGo) notArgToGo := 1 - go takesArg(argToGo) // want "missing whitespace decreases readability" + go Fn(argToGo) // want "missing whitespace decreases readability" + + _ = notArgToGo t1 := NewT() t2 := NewT() @@ -38,7 +51,7 @@ func Go() { multiCuddle1 := NewT() multiCuddle2 := NewT() // want "missing whitespace decreases readability" - go multiCuddle2() + go multiCuddle2() // TODO: Breaking change, this used to be on the first `go` stmt - now it's // on the line that should have a blank line above. @@ -47,4 +60,12 @@ func Go() { t5 := NewT() // want "missing whitespace decreases readability" go t5() go t4() + + _ = t1 + _ = t2 + _ = t3 + _ = t4 + _ = t5 + _ = multiCuddle1 + _ = multiCuddle2 } diff --git a/testdata/src/default_config/if.go b/testdata/src/default_config/if.go deleted file mode 100644 index c4775e6..0000000 --- a/testdata/src/default_config/if.go +++ /dev/null @@ -1,36 +0,0 @@ -package testpkg - -import "errors" - -func fn1() { - one := 1 - two := 2 // want "missing whitespace decreases readability" - if two == 2 { - panic(err) - } -} - -func fn2() { - two := 2 - one := 1 - if two == 2 { // want "missing whitespace decreases readability" - panic(err) - } -} - -func fn3() { - err := errors.New("error") - - if err != nil { - panic(err) - } -} - -func fn4() { - if foo := 1; foo != 2 { - panic(foo) - } - if foo := 2; foo != 2 { // want "missing whitespace decreases readability" - panic(bar) - } -} diff --git a/testdata/src/default_config/if.go.golden b/testdata/src/default_config/if.go.golden deleted file mode 100644 index da1bdca..0000000 --- a/testdata/src/default_config/if.go.golden +++ /dev/null @@ -1,39 +0,0 @@ -package testpkg - -import "errors" - -func fn1() { - one := 1 - - two := 2 // want "missing whitespace decreases readability" - if two == 2 { - panic(err) - } -} - -func fn2() { - two := 2 - one := 1 - - if two == 2 { // want "missing whitespace decreases readability" - panic(err) - } -} - -func fn3() { - err := errors.New("error") - - if err != nil { - panic(err) - } -} - -func fn4() { - if foo := 1; foo != 2 { - panic(foo) - } - - if foo := 2; foo != 2 { // want "missing whitespace decreases readability" - panic(bar) - } -} diff --git a/testdata/src/default_config/if/if.go b/testdata/src/default_config/if/if.go new file mode 100644 index 0000000..48e5adc --- /dev/null +++ b/testdata/src/default_config/if/if.go @@ -0,0 +1,36 @@ +package testpkg + +import "errors" + +func fn1() { + a := 1 + b := 2 // want "missing whitespace decreases readability" + if b == 2 { + panic(a) + } +} + +func fn2() { + b := 2 + a := 1 + if b == 2 { // want "missing whitespace decreases readability" + panic(a) + } +} + +func fn3() { + err := errors.New("error") + + if err != nil { + panic(err) + } +} + +func fn4() { + if a := 1; a != 2 { + panic(a) + } + if a := 2; a != 2 { // want "missing whitespace decreases readability" + panic(a) + } +} diff --git a/testdata/src/default_config/if/if.go.golden b/testdata/src/default_config/if/if.go.golden new file mode 100644 index 0000000..d6c15e6 --- /dev/null +++ b/testdata/src/default_config/if/if.go.golden @@ -0,0 +1,39 @@ +package testpkg + +import "errors" + +func fn1() { + a := 1 + + b := 2 // want "missing whitespace decreases readability" + if b == 2 { + panic(a) + } +} + +func fn2() { + b := 2 + a := 1 + + if b == 2 { // want "missing whitespace decreases readability" + panic(a) + } +} + +func fn3() { + err := errors.New("error") + + if err != nil { + panic(err) + } +} + +func fn4() { + if a := 1; a != 2 { + panic(a) + } + + if a := 2; a != 2 { // want "missing whitespace decreases readability" + panic(a) + } +} diff --git a/testdata/src/default_config/range.go b/testdata/src/default_config/range.go deleted file mode 100644 index d139cc1..0000000 --- a/testdata/src/default_config/range.go +++ /dev/null @@ -1,26 +0,0 @@ -package testpkg - -func fn1() { - one := []int{} - two := []int{} // want "missing whitespace decreases readability" - for range two { - panic(err) - } -} - -func fn2() { - two := []int{} - one := []int{} - for range two { // want "missing whitespace decreases readability" - panic(err) - } -} - -func fn3() { - for range make([]int{}, 0) { - panic(foo) - } - for range make([]int{}, 0) { // want "missing whitespace decreases readability" - panic(bar) - } -} diff --git a/testdata/src/default_config/range.go.golden b/testdata/src/default_config/range.go.golden deleted file mode 100644 index 7ee405c..0000000 --- a/testdata/src/default_config/range.go.golden +++ /dev/null @@ -1,30 +0,0 @@ -package testpkg - -func fn1() { - one := []int{} - - two := []int{} // want "missing whitespace decreases readability" - for range two { - panic(err) - } -} - -func fn2() { - two := []int{} - one := []int{} - - for range two { // want "missing whitespace decreases readability" - panic(err) - } -} - -func fn3() { - for range make([]int{}, 0) { - panic(foo) - } - - for range make([]int{}, 0) { // want "missing whitespace decreases readability" - panic(bar) - } -} - diff --git a/testdata/src/default_config/range/range.go b/testdata/src/default_config/range/range.go new file mode 100644 index 0000000..1a8a0b1 --- /dev/null +++ b/testdata/src/default_config/range/range.go @@ -0,0 +1,26 @@ +package testpkg + +func fn1() { + a := []int{} + b := []int{} // want "missing whitespace decreases readability" + for range b { + panic(a) + } +} + +func fn2() { + b := []int{} + a := []int{} + for range b { // want "missing whitespace decreases readability" + panic(a) + } +} + +func fn3() { + for range make([]int, 0) { + panic("") + } + for range make([]int, 0) { // want "missing whitespace decreases readability" + panic("") + } +} diff --git a/testdata/src/default_config/range/range.go.golden b/testdata/src/default_config/range/range.go.golden new file mode 100644 index 0000000..427a22d --- /dev/null +++ b/testdata/src/default_config/range/range.go.golden @@ -0,0 +1,29 @@ +package testpkg + +func fn1() { + a := []int{} + + b := []int{} // want "missing whitespace decreases readability" + for range b { + panic(a) + } +} + +func fn2() { + b := []int{} + a := []int{} + + for range b { // want "missing whitespace decreases readability" + panic(a) + } +} + +func fn3() { + for range make([]int, 0) { + panic("") + } + + for range make([]int, 0) { // want "missing whitespace decreases readability" + panic("") + } +} diff --git a/testdata/src/default_config/return.go b/testdata/src/default_config/return/return.go similarity index 100% rename from testdata/src/default_config/return.go rename to testdata/src/default_config/return/return.go diff --git a/testdata/src/default_config/return.go.golden b/testdata/src/default_config/return/return.go.golden similarity index 100% rename from testdata/src/default_config/return.go.golden rename to testdata/src/default_config/return/return.go.golden diff --git a/testdata/src/default_config/switch.go b/testdata/src/default_config/switch/switch.go similarity index 55% rename from testdata/src/default_config/switch.go rename to testdata/src/default_config/switch/switch.go index 6406a5f..608f56a 100644 --- a/testdata/src/default_config/switch.go +++ b/testdata/src/default_config/switch/switch.go @@ -1,24 +1,24 @@ package testpkg func fn1() { - one := 1 - two := 2 // want "missing whitespace decreases readability" - switch two { + a := 1 + b := 2 // want "missing whitespace decreases readability" + switch b { case 1: case 2: case 3: - panic(err) + panic(a) } } func fn2() { - two := 2 - one := 1 - switch two { // want "missing whitespace decreases readability" + b := 2 + a := 1 + switch b { // want "missing whitespace decreases readability" case 1: case 2: case 3: - panic(err) + panic(a) } } @@ -30,6 +30,6 @@ func fn3() { switch true { // want "missing whitespace decreases readability" case true: case false: - panic(err) + panic("") } } diff --git a/testdata/src/default_config/switch.go.golden b/testdata/src/default_config/switch/switch.go.golden similarity index 55% rename from testdata/src/default_config/switch.go.golden rename to testdata/src/default_config/switch/switch.go.golden index 126d6a3..0804498 100644 --- a/testdata/src/default_config/switch.go.golden +++ b/testdata/src/default_config/switch/switch.go.golden @@ -1,26 +1,26 @@ package testpkg func fn1() { - one := 1 + a := 1 - two := 2 // want "missing whitespace decreases readability" - switch two { + b := 2 // want "missing whitespace decreases readability" + switch b { case 1: case 2: case 3: - panic(err) + panic(a) } } func fn2() { - two := 2 - one := 1 + b := 2 + a := 1 - switch two { // want "missing whitespace decreases readability" + switch b { // want "missing whitespace decreases readability" case 1: case 2: case 3: - panic(err) + panic(a) } } @@ -33,7 +33,7 @@ func fn3() { switch true { // want "missing whitespace decreases readability" case true: case false: - panic(err) + panic("") } } diff --git a/testdata/src/default_config/type_switch.go.golden b/testdata/src/default_config/type_switch/type_switch.go similarity index 52% rename from testdata/src/default_config/type_switch.go.golden rename to testdata/src/default_config/type_switch/type_switch.go index e3bcd9b..d02fdb8 100644 --- a/testdata/src/default_config/type_switch.go.golden +++ b/testdata/src/default_config/type_switch/type_switch.go @@ -1,5 +1,9 @@ package testpkg +func Fn() any { + return nil +} + func fn1() { var v any @@ -11,15 +15,16 @@ func fn1() { } func fn2() { - var v any - v = 1 + var a any + a = 1 - one := 1 - - switch v.(type) { // want "missing whitespace decreases readability" + b := 1 + switch a.(type) { // want "missing whitespace decreases readability" case int: case string: } + + _ = b } func fn3(in any) { @@ -27,20 +32,22 @@ func fn3(in any) { return } - switch v := in.(type) { + switch a := in.(type) { case string: return default: + _ = a } } func fn4(in any) { - one := getOne() - - two := getTwo() // want "missing whitespace decreases readability" - switch two.(type) { + a := Fn() + b := Fn() // want "missing whitespace decreases readability" + switch b.(type) { case string: return default: + + _ = a } } diff --git a/testdata/src/default_config/type_switch.go b/testdata/src/default_config/type_switch/type_switch.go.golden similarity index 52% rename from testdata/src/default_config/type_switch.go rename to testdata/src/default_config/type_switch/type_switch.go.golden index ab068ce..498c54d 100644 --- a/testdata/src/default_config/type_switch.go +++ b/testdata/src/default_config/type_switch/type_switch.go.golden @@ -1,5 +1,9 @@ package testpkg +func Fn() any { + return nil +} + func fn1() { var v any @@ -11,14 +15,17 @@ func fn1() { } func fn2() { - var v any - v = 1 + var a any + a = 1 + + b := 1 - one := 1 - switch v.(type) { // want "missing whitespace decreases readability" + switch a.(type) { // want "missing whitespace decreases readability" case int: case string: } + + _ = b } func fn3(in any) { @@ -26,19 +33,24 @@ func fn3(in any) { return } - switch v := in.(type) { + switch a := in.(type) { case string: return default: + _ = a } } func fn4(in any) { - one := getOne() - two := getTwo() // want "missing whitespace decreases readability" - switch two.(type) { + a := Fn() + + b := Fn() // want "missing whitespace decreases readability" + switch b.(type) { case string: return default: + + _ = a } } + diff --git a/testdata/src/default_config/leading_whitespace.go b/testdata/src/default_config/whitespace/leading_whitespace.go similarity index 91% rename from testdata/src/default_config/leading_whitespace.go rename to testdata/src/default_config/whitespace/leading_whitespace.go index fbd549d..29edbf1 100644 --- a/testdata/src/default_config/leading_whitespace.go +++ b/testdata/src/default_config/whitespace/leading_whitespace.go @@ -2,6 +2,10 @@ package testpkg import "fmt" +func Call(fn func()) func() { + return fn +} + func fn1() { // want "unnecessary whitespace decreases readability" fmt.Println("Hello, World") @@ -69,9 +73,9 @@ func fn8(a string, b any, s []string) { // want "unnecessary whitespace decrease _ = 1 } - for n := range 10 { // want "unnecessary whitespace decreases readability" + for n := range []int{} { // want "unnecessary whitespace decreases readability" - _ = 1 + _ = n } for range s { // want "unnecessary whitespace decreases readability" @@ -98,4 +102,7 @@ func fn8(a string, b any, s []string) { // want "unnecessary whitespace decrease _ = 1 }) + + _ = f + _ = f2 } diff --git a/testdata/src/default_config/leading_whitespace.go.golden b/testdata/src/default_config/whitespace/leading_whitespace.go.golden similarity index 91% rename from testdata/src/default_config/leading_whitespace.go.golden rename to testdata/src/default_config/whitespace/leading_whitespace.go.golden index 6d6621b..31bfbd3 100644 --- a/testdata/src/default_config/leading_whitespace.go.golden +++ b/testdata/src/default_config/whitespace/leading_whitespace.go.golden @@ -2,6 +2,10 @@ package testpkg import "fmt" +func Call(fn func()) func() { + return fn +} + func fn1() { // want "unnecessary whitespace decreases readability" fmt.Println("Hello, World") } @@ -59,8 +63,8 @@ func fn8(a string, b any, s []string) { // want "unnecessary whitespace decrease _ = 1 } - for n := range 10 { // want "unnecessary whitespace decreases readability" - _ = 1 + for n := range []int{} { // want "unnecessary whitespace decreases readability" + _ = n } for range s { // want "unnecessary whitespace decreases readability" @@ -82,4 +86,7 @@ func fn8(a string, b any, s []string) { // want "unnecessary whitespace decrease f2 := Call(func() { // want "unnecessary whitespace decreases readability" _ = 1 }) + + _ = f + _ = f2 } diff --git a/testdata/src/wip/wip.go b/testdata/src/wip/wip.go deleted file mode 100644 index a21e3cf..0000000 --- a/testdata/src/wip/wip.go +++ /dev/null @@ -1 +0,0 @@ -package testpkg diff --git a/testdata/src/wip/wip.go.golden b/testdata/src/wip/wip.go.golden deleted file mode 100644 index a21e3cf..0000000 --- a/testdata/src/wip/wip.go.golden +++ /dev/null @@ -1 +0,0 @@ -package testpkg diff --git a/testdata/src/with_config/if_errcheck/if_errcheck.go b/testdata/src/with_config/if_errcheck/if_errcheck.go index 8035538..66c7a70 100644 --- a/testdata/src/with_config/if_errcheck/if_errcheck.go +++ b/testdata/src/with_config/if_errcheck/if_errcheck.go @@ -11,24 +11,29 @@ func fn1() { } func fn2() { - one := 1 + a := 1 err := errors.New("x") if err != nil { // want "unnecessary whitespace decreases readability" panic(err) } + + _ = a } func fn3() { - one := 1 + a := 1 err := errors.New("x") // want "missing whitespace decreases readability" if err != nil { panic(err) } + + _ = a } func fn4() { - err := "not an error" + msg := "not an error" + err := &msg if err != nil { panic(err) diff --git a/testdata/src/with_config/if_errcheck/if_errcheck.go.golden b/testdata/src/with_config/if_errcheck/if_errcheck.go.golden index 9d36536..642cc78 100644 --- a/testdata/src/with_config/if_errcheck/if_errcheck.go.golden +++ b/testdata/src/with_config/if_errcheck/if_errcheck.go.golden @@ -1,3 +1,4 @@ + package testpkg import "errors" @@ -10,25 +11,30 @@ func fn1() { } func fn2() { - one := 1 + a := 1 err := errors.New("x") if err != nil { // want "unnecessary whitespace decreases readability" panic(err) } + + _ = a } func fn3() { - one := 1 + a := 1 err := errors.New("x") // want "missing whitespace decreases readability" if err != nil { panic(err) } + + _ = a } func fn4() { - err := "not an error" + msg := "not an error" + err := &msg if err != nil { panic(err) diff --git a/testdata/src/with_config/no_check_decl/no_check_decl.go b/testdata/src/with_config/no_check_decl/no_check_decl.go index 5a635e5..6e9334d 100644 --- a/testdata/src/with_config/no_check_decl/no_check_decl.go +++ b/testdata/src/with_config/no_check_decl/no_check_decl.go @@ -5,4 +5,9 @@ func fn1() { var b = 2 const c = 3 const d = 4 + + _ = a + _ = b + _ = c + _ = d } diff --git a/testdata/src/with_config/no_check_decl/no_check_decl.go.golden b/testdata/src/with_config/no_check_decl/no_check_decl.go.golden index c682dc2..7a8de41 100644 --- a/testdata/src/with_config/no_check_decl/no_check_decl.go.golden +++ b/testdata/src/with_config/no_check_decl/no_check_decl.go.golden @@ -5,5 +5,10 @@ func fn1() { var b = 2 const c = 3 const d = 4 + + _ = a + _ = b + _ = c + _ = d } diff --git a/wsl.go b/wsl.go index d1464f3..140a09a 100644 --- a/wsl.go +++ b/wsl.go @@ -241,7 +241,14 @@ func (w *WSL) CheckTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { } func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { - w.CheckCuddling(stmt, cursor, -1) + previousNode := cursor.PreviousNode() + + // We can cuddle any amount call statements so only check cuddling if the + // previous one isn't a function call. + if _, ok := previousNode.(*ast.ExprStmt); !ok { + w.CheckCuddling(stmt, cursor, -1) + } + w.CheckExpr(stmt.X, cursor) } @@ -405,7 +412,7 @@ func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) { for _, e := range s.Args { w.CheckExpr(e, cursor) } - case *ast.BasicLit, *ast.CompositeLit, *ast.Ident: + case *ast.BasicLit, *ast.CompositeLit, *ast.Ident, *ast.UnaryExpr: default: fmt.Printf("Not implemented expr: %T\n", s) } @@ -575,6 +582,10 @@ func allIdents(node ast.Node) []*ast.Ident { idents = append(idents, allIdents(n.Y)...) case *ast.RangeStmt: idents = append(idents, allIdents(n.X)...) + case *ast.SelectorExpr: + idents = append(idents, allIdents(n.X)...) + case *ast.UnaryExpr: + idents = append(idents, allIdents(n.X)...) case *ast.ForStmt: idents = append(idents, allIdents(n.Init)...) idents = append(idents, allIdents(n.Cond)...) @@ -596,7 +607,8 @@ func allIdents(node ast.Node) []*ast.Ident { for _, elt := range n.Elts { idents = append(idents, allIdents(elt)...) } - case *ast.BasicLit, *ast.FuncLit, *ast.IncDecStmt, *ast.BranchStmt: + case *ast.BasicLit, *ast.FuncLit, *ast.IncDecStmt, *ast.BranchStmt, + *ast.ArrayType: default: spew.Dump(node) fmt.Printf("%T\n", node) diff --git a/wsl_test.go b/wsl_test.go index 9ddae7d..0ebdb4d 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -1,6 +1,7 @@ package wsl import ( + "os" "path/filepath" "testing" @@ -8,10 +9,19 @@ import ( ) func TestDefaultConfig(t *testing.T) { - testdata := analysistest.TestData() - analyzer := NewAnalyzer(NewConfig()) + dirs, err := os.ReadDir("./testdata/src/default_config") + if err != nil { + t.Fatal(err) + } + + for _, tc := range dirs { + t.Run(tc.Name(), func(t *testing.T) { + testdata := analysistest.TestData() + analyzer := NewAnalyzer(NewConfig()) - analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "default_config") + analysistest.RunWithSuggestedFixes(t, testdata, analyzer, filepath.Join("default_config", tc.Name())) + }) + } } func TestWithConfig(t *testing.T) { @@ -43,10 +53,3 @@ func TestWithConfig(t *testing.T) { }) } } - -func TestWIP(t *testing.T) { - testdata := analysistest.TestData() - analyzer := NewAnalyzer(NewConfig()) - - analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "wip") -} From 5b7b0bd891b0c6dab460035a0b74789249da0262 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 1 Jan 2025 17:57:50 +0100 Subject: [PATCH 023/112] Defer --- testdata/src/default_config/defer/defer.go | 60 +++++++++++++++++ .../src/default_config/defer/defer.go.golden | 64 +++++++++++++++++++ wsl.go | 20 +++++- 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 testdata/src/default_config/defer/defer.go create mode 100644 testdata/src/default_config/defer/defer.go.golden diff --git a/testdata/src/default_config/defer/defer.go b/testdata/src/default_config/defer/defer.go new file mode 100644 index 0000000..d286bc1 --- /dev/null +++ b/testdata/src/default_config/defer/defer.go @@ -0,0 +1,60 @@ +package testpkg + +import ( + "fmt" + "sync" +) + +type T int + +func Fn() T { + return T(0) +} + +func (*T) Close() {} + +func Fn() {} + +func fn() { + a := Fn() + b := Fn() + + defer a.Close() + defer b.Close() + + a = Fn() + defer a.Close() + + b = Fn() + defer b.Close() + + a = Fn() + defer a.Close() + b = Fn() // want "missing whitespace decreases readability" + defer b.Close() + + a = Fn() + b = Fn() + defer a.Close() // want "missing whitespace decreases readability" + defer b.Close() + + m := sync.Mutex{} + + m.Lock() + defer m.Unlock() + + c := true + defer func(b bool) { // want "missing whitespace decreases readability" + fmt.Printf("%v", b) + }() + + _ = c +} + +func fn2() { + a := 1 + b := Fn() // want "missing whitespace decreases readability" + defer b.Close() + + _ = a +} diff --git a/testdata/src/default_config/defer/defer.go.golden b/testdata/src/default_config/defer/defer.go.golden new file mode 100644 index 0000000..a43ac44 --- /dev/null +++ b/testdata/src/default_config/defer/defer.go.golden @@ -0,0 +1,64 @@ +package testpkg + +import ( + "fmt" + "sync" +) + +type T int + +func Fn() T { + return T(0) +} + +func (*T) Close() {} + +func Fn() {} + +func fn() { + a := Fn() + b := Fn() + + defer a.Close() + defer b.Close() + + a = Fn() + defer a.Close() + + b = Fn() + defer b.Close() + + a = Fn() + defer a.Close() + + b = Fn() // want "missing whitespace decreases readability" + defer b.Close() + + a = Fn() + b = Fn() + + defer a.Close() // want "missing whitespace decreases readability" + defer b.Close() + + m := sync.Mutex{} + + m.Lock() + defer m.Unlock() + + c := true + + defer func(b bool) { // want "missing whitespace decreases readability" + fmt.Printf("%v", b) + }() + + _ = c +} + +func fn2() { + a := 1 + + b := Fn() // want "missing whitespace decreases readability" + defer b.Close() + + _ = a +} diff --git a/wsl.go b/wsl.go index 140a09a..7c1a465 100644 --- a/wsl.go +++ b/wsl.go @@ -103,10 +103,11 @@ func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements _, prevIsAssign := previousNode.(*ast.AssignStmt) _, prevIsDecl := previousNode.(*ast.DeclStmt) + _, currIsDefer := stmt.(*ast.DeferStmt) n := w.numberOfStatementsAbove(cursor) if n > 0 { - if prevIsAssign || prevIsDecl { + if prevIsAssign || prevIsDecl || currIsDefer { intersects := identIntersection(currentIdents, previousIdents) // No idents above share name with one in the if statement. @@ -266,6 +267,18 @@ func (w *WSL) CheckGo(stmt *ast.GoStmt, cursor *Cursor) { w.CheckExpr(stmt.Call, cursor) } +func (w *WSL) CheckDefer(stmt *ast.DeferStmt, cursor *Cursor) { + previousNode := cursor.PreviousNode() + + // We can cuddle any amount `defer` statements so only check cuddling if the + // previous one isn't a `defer` call. + if _, ok := previousNode.(*ast.DeferStmt); !ok { + w.CheckCuddling(stmt, cursor, 1) + } + + w.CheckExpr(stmt.Call, cursor) +} + func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { if _, ok := w.Config.Checks[CheckBreak]; !ok && stmt.Tok == token.BREAK { return @@ -389,6 +402,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { case *ast.IncDecStmt: // defer func() {} case *ast.DeferStmt: + w.CheckDefer(s, cursor) // go func() {} case *ast.GoStmt: w.CheckGo(s, cursor) @@ -550,6 +564,8 @@ func allIdents(node ast.Node) []*ast.Ident { } case *ast.GoStmt: idents = append(idents, allIdents(n.Call)...) + case *ast.DeferStmt: + idents = append(idents, allIdents(n.Call)...) case *ast.ValueSpec: for _, name := range n.Names { idents = append(idents, allIdents(name)...) @@ -611,7 +627,7 @@ func allIdents(node ast.Node) []*ast.Ident { *ast.ArrayType: default: spew.Dump(node) - fmt.Printf("%T\n", node) + fmt.Printf("missing ident detection for %T\n", node) } return idents From 698c6a1cf11e1efe4d62e003bf0da7c5c2dff426 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 2 Jan 2025 19:48:52 +0100 Subject: [PATCH 024/112] Support to enable/disable more checks --- wsl.go | 104 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/wsl.go b/wsl.go index 7c1a465..e1e91ba 100644 --- a/wsl.go +++ b/wsl.go @@ -22,11 +22,20 @@ type CheckType int // Each checker is represented by a CheckType that is used to enable or disable // the check. const ( - CheckDecl CheckType = iota + CheckAssign CheckType = iota CheckBreak CheckContinue + CheckDecl + CheckDefer + CheckExpr + CheckFor + CheckGo CheckIf CheckLeadingWhitespace + CheckRange + CheckReturn + CheckSwitch + CheckTypeSwitch ) type Configuration struct { @@ -39,11 +48,20 @@ func NewConfig() *Configuration { return &Configuration{ Errcheck: false, Checks: map[CheckType]struct{}{ + CheckAssign: {}, CheckBreak: {}, CheckContinue: {}, CheckDecl: {}, + CheckDefer: {}, + CheckExpr: {}, + CheckFor: {}, + CheckGo: {}, CheckIf: {}, CheckLeadingWhitespace: {}, + CheckRange: {}, + CheckReturn: {}, + CheckSwitch: {}, + CheckTypeSwitch: {}, }, } } @@ -202,12 +220,10 @@ func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { } func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { - if _, ok := w.Config.Checks[CheckIf]; !ok { - return + if _, ok := w.Config.Checks[CheckIf]; ok { + w.CheckCuddling(stmt, cursor, 1) } - w.CheckCuddling(stmt, cursor, 1) - // if w.CheckBlock(stmt.Body) @@ -222,26 +238,52 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { } func (w *WSL) CheckFor(stmt *ast.ForStmt, cursor *Cursor) { + defer w.CheckBlock(stmt.Body) + + if _, ok := w.Config.Checks[CheckFor]; !ok { + return + } + w.CheckCuddling(stmt, cursor, 1) - w.CheckBlock(stmt.Body) } func (w *WSL) CheckRange(stmt *ast.RangeStmt, cursor *Cursor) { + defer w.CheckBlock(stmt.Body) + + if _, ok := w.Config.Checks[CheckRange]; !ok { + return + } + w.CheckCuddling(stmt, cursor, 1) - w.CheckBlock(stmt.Body) } func (w *WSL) CheckSwitch(stmt *ast.SwitchStmt, cursor *Cursor) { + defer w.CheckBlock(stmt.Body) + + if _, ok := w.Config.Checks[CheckSwitch]; !ok { + return + } + w.CheckCuddling(stmt, cursor, 1) - w.CheckBlock(stmt.Body) } func (w *WSL) CheckTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { + defer w.CheckBlock(stmt.Body) + + if _, ok := w.Config.Checks[CheckTypeSwitch]; !ok { + return + } + w.CheckCuddling(stmt, cursor, 1) - w.CheckBlock(stmt.Body) } func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { + defer w.CheckExpr(stmt.X, cursor) + + if _, ok := w.Config.Checks[CheckExpr]; !ok { + return + } + previousNode := cursor.PreviousNode() // We can cuddle any amount call statements so only check cuddling if the @@ -249,11 +291,15 @@ func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { if _, ok := previousNode.(*ast.ExprStmt); !ok { w.CheckCuddling(stmt, cursor, -1) } - - w.CheckExpr(stmt.X, cursor) } func (w *WSL) CheckGo(stmt *ast.GoStmt, cursor *Cursor) { + defer w.CheckExpr(stmt.Call, cursor) + + if _, ok := w.Config.Checks[CheckGo]; !ok { + return + } + previousNode := cursor.PreviousNode() // We can cuddle any amount `go` statements so only check cuddling if the @@ -263,11 +309,15 @@ func (w *WSL) CheckGo(stmt *ast.GoStmt, cursor *Cursor) { if _, ok := previousNode.(*ast.GoStmt); !ok { w.CheckCuddling(stmt, cursor, 1) } - - w.CheckExpr(stmt.Call, cursor) } func (w *WSL) CheckDefer(stmt *ast.DeferStmt, cursor *Cursor) { + defer w.CheckExpr(stmt.Call, cursor) + + if _, ok := w.Config.Checks[CheckDefer]; !ok { + return + } + previousNode := cursor.PreviousNode() // We can cuddle any amount `defer` statements so only check cuddling if the @@ -275,8 +325,6 @@ func (w *WSL) CheckDefer(stmt *ast.DeferStmt, cursor *Cursor) { if _, ok := previousNode.(*ast.DeferStmt); !ok { w.CheckCuddling(stmt, cursor, 1) } - - w.CheckExpr(stmt.Call, cursor) } func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { @@ -328,6 +376,10 @@ func (w *WSL) CheckBlock(block *ast.BlockStmt) { } func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { + if _, ok := w.Config.Checks[CheckReturn]; !ok { + return + } + // There's only a return statement. noStmts := cursor.Len() if noStmts <= 1 { @@ -350,17 +402,19 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { } func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { - previousNode := cursor.PreviousNode() - _, prevIsAssign := previousNode.(*ast.AssignStmt) - _, prevIsDecl := previousNode.(*ast.DeclStmt) + if _, ok := w.Config.Checks[CheckAssign]; ok { + previousNode := cursor.PreviousNode() + _, prevIsAssign := previousNode.(*ast.AssignStmt) + _, prevIsDecl := previousNode.(*ast.DeclStmt) - if w.numberOfStatementsAbove(cursor) > 0 && previousNode != nil && !prevIsAssign && !prevIsDecl { - w.addError( - stmt.Pos(), - stmt.Pos(), - stmt.Pos(), - MessageAddWhitespace, - ) + if w.numberOfStatementsAbove(cursor) > 0 && previousNode != nil && !prevIsAssign && !prevIsDecl { + w.addError( + stmt.Pos(), + stmt.Pos(), + stmt.Pos(), + MessageAddWhitespace, + ) + } } for _, expr := range stmt.Rhs { From 124122582a60f5c9307b66ac0624da772490b255 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 2 Jan 2025 20:06:26 +0100 Subject: [PATCH 025/112] Trailing newline --- .../whitespace/trailing_whitespace.go | 98 +++++++++++++++++++ .../whitespace/trailing_whitespace.go.golden | 86 ++++++++++++++++ wsl.go | 69 ++++++++++--- 3 files changed, 239 insertions(+), 14 deletions(-) create mode 100644 testdata/src/default_config/whitespace/trailing_whitespace.go create mode 100644 testdata/src/default_config/whitespace/trailing_whitespace.go.golden diff --git a/testdata/src/default_config/whitespace/trailing_whitespace.go b/testdata/src/default_config/whitespace/trailing_whitespace.go new file mode 100644 index 0000000..a62bc6a --- /dev/null +++ b/testdata/src/default_config/whitespace/trailing_whitespace.go @@ -0,0 +1,98 @@ +package testpkg + +import "fmt" + +func Call(fn func()) func() { + return fn +} + +func fn1() { + fmt.Println("Hello, World") + +} // want "unnecessary whitespace decreases readability" + +func fn2() { + fmt.Println("Hello, World") + // Comment with wihtespace + +} // want "unnecessary whitespace decreases readability" + +func fn3() { + fmt.Println("Hello, World") + // Comment without space before or after. +} + +func fn4() { + _ = 1 + // Comment with + + // a newline between +} + +func fn5() { + if true { + _ = 1 + + } // want "unnecessary whitespace decreases readability" +} + +func fn6() { + if true { + _ = 1 + + } else if true { // want "unnecessary whitespace decreases readability" + _ = 1 + + } else { // want "unnecessary whitespace decreases readability" + _ = 1 + + } // want "unnecessary whitespace decreases readability" +} + +func fn8(a string, b any, s []string) { + if true { + _ = 1 + + } else if true { // want "unnecessary whitespace decreases readability" + _ = 1 + + } // want "unnecessary whitespace decreases readability" + + for i := 0; i < 1; i++ { + _ = 1 + + } // want "unnecessary whitespace decreases readability" + + for n := range []int{} { + _ = n + + } // want "unnecessary whitespace decreases readability" + + for range s { + _ = 1 + + } // want "unnecessary whitespace decreases readability" + + switch a { + case "a": + + } + + switch b.(type) { + case int: + + } + + f := func() { + _ = 1 + + } // want "unnecessary whitespace decreases readability" + + f2 := Call(func() { + _ = 1 + + }) // want "unnecessary whitespace decreases readability" + + _ = f + _ = f2 +} diff --git a/testdata/src/default_config/whitespace/trailing_whitespace.go.golden b/testdata/src/default_config/whitespace/trailing_whitespace.go.golden new file mode 100644 index 0000000..0814499 --- /dev/null +++ b/testdata/src/default_config/whitespace/trailing_whitespace.go.golden @@ -0,0 +1,86 @@ +package testpkg + +import "fmt" + +func Call(fn func()) func() { + return fn +} + +func fn1() { + fmt.Println("Hello, World") +} // want "unnecessary whitespace decreases readability" + +func fn2() { + fmt.Println("Hello, World") + // Comment with wihtespace +} // want "unnecessary whitespace decreases readability" + +func fn3() { + fmt.Println("Hello, World") + // Comment without space before or after. +} + +func fn4() { + _ = 1 + // Comment with + + // a newline between +} + +func fn5() { + if true { + _ = 1 + } // want "unnecessary whitespace decreases readability" +} + +func fn6() { + if true { + _ = 1 + } else if true { // want "unnecessary whitespace decreases readability" + _ = 1 + } else { // want "unnecessary whitespace decreases readability" + _ = 1 + } // want "unnecessary whitespace decreases readability" +} + +func fn8(a string, b any, s []string) { + if true { + _ = 1 + } else if true { // want "unnecessary whitespace decreases readability" + _ = 1 + } // want "unnecessary whitespace decreases readability" + + for i := 0; i < 1; i++ { + _ = 1 + } // want "unnecessary whitespace decreases readability" + + for n := range []int{} { + _ = n + } // want "unnecessary whitespace decreases readability" + + for range s { + _ = 1 + } // want "unnecessary whitespace decreases readability" + + switch a { + case "a": + + } + + switch b.(type) { + case int: + + } + + f := func() { + _ = 1 + } // want "unnecessary whitespace decreases readability" + + f2 := Call(func() { + _ = 1 + }) // want "unnecessary whitespace decreases readability" + + _ = f + _ = f2 +} + diff --git a/wsl.go b/wsl.go index e1e91ba..7744158 100644 --- a/wsl.go +++ b/wsl.go @@ -32,6 +32,7 @@ const ( CheckGo CheckIf CheckLeadingWhitespace + CheckTrailingWhitespace CheckRange CheckReturn CheckSwitch @@ -48,20 +49,21 @@ func NewConfig() *Configuration { return &Configuration{ Errcheck: false, Checks: map[CheckType]struct{}{ - CheckAssign: {}, - CheckBreak: {}, - CheckContinue: {}, - CheckDecl: {}, - CheckDefer: {}, - CheckExpr: {}, - CheckFor: {}, - CheckGo: {}, - CheckIf: {}, - CheckLeadingWhitespace: {}, - CheckRange: {}, - CheckReturn: {}, - CheckSwitch: {}, - CheckTypeSwitch: {}, + CheckAssign: {}, + CheckBreak: {}, + CheckContinue: {}, + CheckDecl: {}, + CheckDefer: {}, + CheckExpr: {}, + CheckFor: {}, + CheckGo: {}, + CheckIf: {}, + CheckLeadingWhitespace: {}, + CheckTrailingWhitespace: {}, + CheckRange: {}, + CheckReturn: {}, + CheckSwitch: {}, + CheckTypeSwitch: {}, }, } } @@ -367,6 +369,7 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { func (w *WSL) CheckBlock(block *ast.BlockStmt) { w.CheckUnnecessaryBlockLeadingNewline(block) + w.CheckUnnecessaryBlockTrailingNewline(block) cursor := NewCursor(-1, block.List) for cursor.Next() { @@ -566,6 +569,44 @@ func (w *WSL) CheckUnnecessaryBlockLeadingNewline(body *ast.BlockStmt) { } } +func (w *WSL) CheckUnnecessaryBlockTrailingNewline(body *ast.BlockStmt) { + if _, ok := w.Config.Checks[CheckTrailingWhitespace]; !ok { + return + } + + // No statements in the block, let's leave it as is. + if len(body.List) == 0 { + return + } + + lastStmt := body.List[len(body.List)-1] + + // We don't want to force removal of the empty line for the last case since + // it can be use for consistency and readability. + if _, ok := lastStmt.(*ast.CaseClause); ok { + return + } + + closingPos := body.Rbrace + lastStmtOrComment := lastStmt.End() + + comments := ast.NewCommentMap(w.Fset, body, w.File.Comments) + for _, commentGroup := range comments { + for _, comment := range commentGroup { + if comment.End() < closingPos && comment.Pos() > lastStmtOrComment { + lastStmtOrComment = comment.End() + } + } + } + + closingPosLine := w.Fset.PositionFor(closingPos, false).Line + lastStmtLine := w.Fset.PositionFor(lastStmtOrComment, false).Line + + if closingPosLine != lastStmtLine+1 { + w.addError(closingPos, lastStmtOrComment, closingPos, MessageRemoveWhitespace) + } +} + func (w *WSL) lineFor(pos token.Pos) int { return w.Fset.PositionFor(pos, false).Line } From abe45cf840f405680849a1526cbfe3e798669608 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 2 Jan 2025 23:05:22 +0100 Subject: [PATCH 026/112] Case clause leading newline --- .../default_config/type_switch/type_switch.go | 1 - .../type_switch/type_switch.go.golden | 1 - .../whitespace/leading_whitespace.go | 19 +++++++++ .../whitespace/leading_whitespace.go.golden | 16 ++++++++ wsl.go | 40 ++++++++++++++----- 5 files changed, 65 insertions(+), 12 deletions(-) diff --git a/testdata/src/default_config/type_switch/type_switch.go b/testdata/src/default_config/type_switch/type_switch.go index d02fdb8..c4e07b0 100644 --- a/testdata/src/default_config/type_switch/type_switch.go +++ b/testdata/src/default_config/type_switch/type_switch.go @@ -47,7 +47,6 @@ func fn4(in any) { case string: return default: - _ = a } } diff --git a/testdata/src/default_config/type_switch/type_switch.go.golden b/testdata/src/default_config/type_switch/type_switch.go.golden index 498c54d..63a3f1c 100644 --- a/testdata/src/default_config/type_switch/type_switch.go.golden +++ b/testdata/src/default_config/type_switch/type_switch.go.golden @@ -49,7 +49,6 @@ func fn4(in any) { case string: return default: - _ = a } } diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go b/testdata/src/default_config/whitespace/leading_whitespace.go index 29edbf1..bb056f7 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go +++ b/testdata/src/default_config/whitespace/leading_whitespace.go @@ -106,3 +106,22 @@ func fn8(a string, b any, s []string) { // want "unnecessary whitespace decrease _ = f _ = f2 } + +func fn9() { + switch { + case 1: // want "unnecessary whitespace decreases readability" + + _ = 1 + + case 2: + // This is a comment // want "unnecessary whitespace decreases readability" + + _ = 2 + + default: // want "unnecessary whitespace decreases readability" + + _ = 3 + + } +} + diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go.golden b/testdata/src/default_config/whitespace/leading_whitespace.go.golden index 31bfbd3..4f63e20 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go.golden +++ b/testdata/src/default_config/whitespace/leading_whitespace.go.golden @@ -90,3 +90,19 @@ func fn8(a string, b any, s []string) { // want "unnecessary whitespace decrease _ = f _ = f2 } + +func fn9() { + switch { + case 1: // want "unnecessary whitespace decreases readability" + _ = 1 + + case 2: + // This is a comment // want "unnecessary whitespace decreases readability" + _ = 2 + + default: // want "unnecessary whitespace decreases readability" + _ = 3 + + } +} + diff --git a/wsl.go b/wsl.go index 7744158..01fa9b7 100644 --- a/wsl.go +++ b/wsl.go @@ -24,6 +24,7 @@ type CheckType int const ( CheckAssign CheckType = iota CheckBreak + CheckCase CheckContinue CheckDecl CheckDefer @@ -51,6 +52,7 @@ func NewConfig() *Configuration { Checks: map[CheckType]struct{}{ CheckAssign: {}, CheckBreak: {}, + CheckCase: {}, CheckContinue: {}, CheckDecl: {}, CheckDefer: {}, @@ -368,7 +370,7 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { } func (w *WSL) CheckBlock(block *ast.BlockStmt) { - w.CheckUnnecessaryBlockLeadingNewline(block) + w.CheckBlockLeadingNewline(block) w.CheckUnnecessaryBlockTrailingNewline(block) cursor := NewCursor(-1, block.List) @@ -425,6 +427,14 @@ func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { } } +func (w *WSL) CheckCase(stmt *ast.CaseClause, cursor *Cursor) { + w.CheckCaseLeadingNewline(stmt) + + if _, ok := w.Config.Checks[CheckCase]; !ok { + return + } +} + func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { //nolint:gocritic // This is not commented out code, it's examples switch s := stmt.(type) { @@ -466,6 +476,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { case *ast.ExprStmt: w.CheckExprStmt(s, cursor) case *ast.CaseClause: + w.CheckCase(s, cursor) case *ast.BlockStmt: w.CheckBlock(s) default: @@ -511,21 +522,30 @@ func (w *WSL) numberOfStatementsAbove(cursor *Cursor) int { return statementsWithoutNewlines } -func (w *WSL) CheckUnnecessaryBlockLeadingNewline(body *ast.BlockStmt) { +func (w *WSL) CheckBlockLeadingNewline(body *ast.BlockStmt) { + comments := ast.NewCommentMap(w.Fset, body, w.File.Comments) + w.CheckNewline(body.Lbrace, body.List, comments) +} + +func (w *WSL) CheckCaseLeadingNewline(case_ *ast.CaseClause) { + comments := ast.NewCommentMap(w.Fset, case_, w.File.Comments) + w.CheckNewline(case_.Colon, case_.Body, comments) +} + +func (w *WSL) CheckNewline(startPos token.Pos, body []ast.Stmt, comments ast.CommentMap) { if _, ok := w.Config.Checks[CheckLeadingWhitespace]; !ok { return } // No statements in the block, let's leave it as is. - if len(body.List) == 0 { + if len(body) == 0 { return } - lbraceLine := w.lineFor(body.Lbrace) - openingPos := body.Lbrace + 1 - firstStmt := body.List[0].Pos() + openLine := w.lineFor(startPos) + openingPos := startPos + 1 + firstStmt := body[0].Pos() - comments := ast.NewCommentMap(w.Fset, body, w.File.Comments) for _, commentGroup := range comments { for _, comment := range commentGroup { // The comment starts after the current opening position (originally @@ -545,11 +565,11 @@ func (w *WSL) CheckUnnecessaryBlockLeadingNewline(body *ast.BlockStmt) { openingPos = comment.End() // Opening position is the same as `{` and the comment is // directly on the line after (no empty line) - case openingPosLine == lbraceLine && - commentStartLine == lbraceLine+1: + case openingPosLine == openLine && + commentStartLine == openLine+1: openingPos = comment.End() // The opening position has been updated, it's another comment. - case openingPosLine != lbraceLine: + case openingPosLine != openLine: openingPos = comment.End() // The opening position is still { and the comment is not // directly above - it must be an empty line which shouldn't be From de755a3711cbb5d142ba1e2be52960729efb4153 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 6 Jan 2025 16:23:17 +0100 Subject: [PATCH 027/112] Append check and some refactoring --- testdata/src/default_config/assign/assign.go | 12 + .../default_config/assign/assign.go.golden | 14 + wsl.go | 259 +++++++++++------- wsl_test.go | 2 +- 4 files changed, 189 insertions(+), 98 deletions(-) create mode 100644 testdata/src/default_config/assign/assign.go create mode 100644 testdata/src/default_config/assign/assign.go.golden diff --git a/testdata/src/default_config/assign/assign.go b/testdata/src/default_config/assign/assign.go new file mode 100644 index 0000000..f70cee2 --- /dev/null +++ b/testdata/src/default_config/assign/assign.go @@ -0,0 +1,12 @@ +package testpkg + +func strictAppend() { + s := []string{} + s = append(s, "a") + s = append(s, "b") + x := "c" + s = append(s, x) + y := "e" + s = append(s, "d") // want "missing whitespace decreases readability" + s = append(s, y) +} diff --git a/testdata/src/default_config/assign/assign.go.golden b/testdata/src/default_config/assign/assign.go.golden new file mode 100644 index 0000000..a3476c5 --- /dev/null +++ b/testdata/src/default_config/assign/assign.go.golden @@ -0,0 +1,14 @@ +package testpkg + +func strictAppend() { + s := []string{} + s = append(s, "a") + s = append(s, "b") + x := "c" + s = append(s, x) + y := "e" + + s = append(s, "d") // want "missing whitespace decreases readability" + s = append(s, y) +} + diff --git a/wsl.go b/wsl.go index 01fa9b7..4f7b928 100644 --- a/wsl.go +++ b/wsl.go @@ -23,12 +23,14 @@ type CheckType int // the check. const ( CheckAssign CheckType = iota + CheckAppend CheckBreak CheckCase CheckContinue CheckDecl CheckDefer CheckExpr + CheckErr CheckFor CheckGo CheckIf @@ -41,16 +43,14 @@ const ( ) type Configuration struct { - // Require no newline between error assignment and error check. - Errcheck bool - Checks map[CheckType]struct{} + Checks map[CheckType]struct{} } func NewConfig() *Configuration { return &Configuration{ - Errcheck: false, Checks: map[CheckType]struct{}{ CheckAssign: {}, + CheckAppend: {}, CheckBreak: {}, CheckCase: {}, CheckContinue: {}, @@ -123,95 +123,110 @@ func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements cursor.Next() // Move forward again } + numStmtsAbove := w.numberOfStatementsAbove(cursor) + + // If we don't have any statements above, we only care about potential error + // cuddling (for if statements) so check that. + if numStmtsAbove == 0 { + w.checkError(numStmtsAbove, stmt, previousNode, previousIdents, cursor) + return + } + _, prevIsAssign := previousNode.(*ast.AssignStmt) _, prevIsDecl := previousNode.(*ast.DeclStmt) _, currIsDefer := stmt.(*ast.DeferStmt) - n := w.numberOfStatementsAbove(cursor) - - if n > 0 { - if prevIsAssign || prevIsDecl || currIsDefer { - intersects := identIntersection(currentIdents, previousIdents) - - // No idents above share name with one in the if statement. - if len(intersects) == 0 { - w.addError( - stmt.Pos(), - stmt.Pos(), - stmt.Pos(), - MessageAddWhitespace, - ) - } - // Check the maximum number of allowed statements above, and if not - // disabled (-1) check that the previous one intersects with the - // current one. - if maxAllowedStatements != -1 && n > maxAllowedStatements { - // Idents on the line above exist in the current condition so that - // should remain cuddled. - if len(intersects) > 0 { - w.addError( - previousNode.Pos(), - previousNode.Pos(), - previousNode.Pos(), - MessageAddWhitespace, - ) - } - // TODO: Features: - // * Allow idents that is used first in the block - // * OR - if configured - anywhere in the block. - } - } else { - // If we have a statement above and it's not an assignment or - // declaration we unconditionally add an error. + // We're cuddled but not with an assign, declare or defer statement which is + // never allowed. + if !prevIsAssign && !prevIsDecl && !currIsDefer { + w.addError(cursor.Stmt().Pos(), cursor.Stmt().Pos(), cursor.Stmt().Pos(), MessageAddWhitespace) + return + } + + // TODO: Features: + // * Allow idents that is used first in the block + // * OR - if configured - anywhere in the block. + + // We're cuddled but the line immediately above doesn't contain any + // variables used in this statement. + intersects := identIntersection(currentIdents, previousIdents) + if len(intersects) == 0 { + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) + return + } + + // Check the maximum number of allowed statements above, and if not + // disabled (-1) check that the previous one intersects with the + // current one. + if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { + // Idents on the line above exist in the current condition so that + // should remain cuddled. + if len(intersects) > 0 { w.addError( - cursor.Stmt().Pos(), - cursor.Stmt().Pos(), - cursor.Stmt().Pos(), + previousNode.Pos(), + previousNode.Pos(), + previousNode.Pos(), MessageAddWhitespace, ) } } +} - if _, ok := stmt.(*ast.IfStmt); !ok { +func (w *WSL) checkError( + stmtsAbove int, + ifStmt ast.Node, + previousNode ast.Node, + previousIdents []*ast.Ident, + cursor *Cursor, +) { + if _, ok := ifStmt.(*ast.IfStmt); !ok { return } - if w.Config.Errcheck && n == 0 && len(previousIdents) > 0 { - if slices.ContainsFunc(previousIdents, func(ident *ast.Ident) bool { - return w.implementsErr(ident) - }) { - w.addError( - stmt.Pos(), - previousNode.End(), - stmt.Pos(), - MessageRemoveWhitespace, - ) + if _, ok := w.Config.Checks[CheckErr]; !ok { + return + } - // If we add the error at the same position but with a different fix - // range, only the fix range will be updated. - // - // a := 1 - // err := fn() - // - // if err != nil {} - // - // Should become - // - // a := 1 - // - // err := fn() - // if err != nil {} - cursor.Previous() - - if w.numberOfStatementsAbove(cursor) > 0 { - w.addError( - stmt.Pos(), - previousNode.Pos(), - previousNode.Pos(), - MessageAddWhitespace, - ) - } - } + if stmtsAbove > 0 || len(previousIdents) == 0 { + return + } + + if !slices.ContainsFunc(previousIdents, func(ident *ast.Ident) bool { + return w.implementsErr(ident) + }) { + return + } + + w.addError( + ifStmt.Pos(), + previousNode.End(), + ifStmt.Pos(), + MessageRemoveWhitespace, + ) + + // If we add the error at the same position but with a different fix + // range, only the fix range will be updated. + // + // a := 1 + // err := fn() + // + // if err != nil {} + // + // Should become + // + // a := 1 + // + // err := fn() + // if err != nil {} + cursor.Previous() + + if w.numberOfStatementsAbove(cursor) > 0 { + w.addError( + ifStmt.Pos(), + previousNode.Pos(), + previousNode.Pos(), + MessageAddWhitespace, + ) } } @@ -371,7 +386,7 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { func (w *WSL) CheckBlock(block *ast.BlockStmt) { w.CheckBlockLeadingNewline(block) - w.CheckUnnecessaryBlockTrailingNewline(block) + w.CheckTrailingNewline(block) cursor := NewCursor(-1, block.List) for cursor.Next() { @@ -407,23 +422,65 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { } func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { - if _, ok := w.Config.Checks[CheckAssign]; ok { - previousNode := cursor.PreviousNode() - _, prevIsAssign := previousNode.(*ast.AssignStmt) - _, prevIsDecl := previousNode.(*ast.DeclStmt) - - if w.numberOfStatementsAbove(cursor) > 0 && previousNode != nil && !prevIsAssign && !prevIsDecl { - w.addError( - stmt.Pos(), - stmt.Pos(), - stmt.Pos(), - MessageAddWhitespace, - ) + defer func() { + for _, expr := range stmt.Rhs { + w.CheckExpr(expr, cursor) } + }() + + if _, ok := w.Config.Checks[CheckAssign]; !ok { + return + } + + previousNode := cursor.PreviousNode() + _, prevIsAssign := previousNode.(*ast.AssignStmt) + _, prevIsDecl := previousNode.(*ast.DeclStmt) + + if w.numberOfStatementsAbove(cursor) > 0 && previousNode != nil && !prevIsAssign && !prevIsDecl { + w.addError( + stmt.Pos(), + stmt.Pos(), + stmt.Pos(), + MessageAddWhitespace, + ) + } + + w.strictAppendCheck(stmt, previousNode) +} + +func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, previousNode ast.Node) { + if _, ok := w.Config.Checks[CheckAppend]; !ok { + return } + var appendNode *ast.CallExpr for _, expr := range stmt.Rhs { - w.CheckExpr(expr, cursor) + e, ok := expr.(*ast.CallExpr) + if !ok { + continue + } + + if f, ok := e.Fun.(*ast.Ident); ok && f.Name == "append" { + appendNode = e + break + } + } + + if appendNode == nil { + return + } + + appendIdents := allIdents(appendNode) + previousIdents := allIdents(previousNode) + intersects := identIntersection(appendIdents, previousIdents) + + if len(intersects) == 0 { + w.addError( + stmt.Pos(), + stmt.Pos(), + stmt.Pos(), + MessageAddWhitespace, + ) } } @@ -433,6 +490,11 @@ func (w *WSL) CheckCase(stmt *ast.CaseClause, cursor *Cursor) { if _, ok := w.Config.Checks[CheckCase]; !ok { return } + + cursor = NewCursor(-1, stmt.Body) + for cursor.Next() { + w.CheckStmt(cursor.Stmt(), cursor) + } } func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { @@ -473,10 +535,13 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { // go func() {} case *ast.GoStmt: w.CheckGo(s, cursor) + // e.g. someFn() case *ast.ExprStmt: w.CheckExprStmt(s, cursor) + // case: case *ast.CaseClause: w.CheckCase(s, cursor) + // { } case *ast.BlockStmt: w.CheckBlock(s) default: @@ -524,15 +589,15 @@ func (w *WSL) numberOfStatementsAbove(cursor *Cursor) int { func (w *WSL) CheckBlockLeadingNewline(body *ast.BlockStmt) { comments := ast.NewCommentMap(w.Fset, body, w.File.Comments) - w.CheckNewline(body.Lbrace, body.List, comments) + w.CheckLeadingNewline(body.Lbrace, body.List, comments) } func (w *WSL) CheckCaseLeadingNewline(case_ *ast.CaseClause) { comments := ast.NewCommentMap(w.Fset, case_, w.File.Comments) - w.CheckNewline(case_.Colon, case_.Body, comments) + w.CheckLeadingNewline(case_.Colon, case_.Body, comments) } -func (w *WSL) CheckNewline(startPos token.Pos, body []ast.Stmt, comments ast.CommentMap) { +func (w *WSL) CheckLeadingNewline(startPos token.Pos, body []ast.Stmt, comments ast.CommentMap) { if _, ok := w.Config.Checks[CheckLeadingWhitespace]; !ok { return } @@ -589,7 +654,7 @@ func (w *WSL) CheckNewline(startPos token.Pos, body []ast.Stmt, comments ast.Com } } -func (w *WSL) CheckUnnecessaryBlockTrailingNewline(body *ast.BlockStmt) { +func (w *WSL) CheckTrailingNewline(body *ast.BlockStmt) { if _, ok := w.Config.Checks[CheckTrailingWhitespace]; !ok { return } diff --git a/wsl_test.go b/wsl_test.go index 0ebdb4d..f0f6efb 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -34,7 +34,7 @@ func TestWithConfig(t *testing.T) { { subdir: "if_errcheck", configFn: func(config *Configuration) { - config.Errcheck = true + config.Checks[CheckErr] = struct{}{} }, }, { From 9003d77f92d59273c14d7e8267d4d1d131c0cf7d Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 6 Jan 2025 16:41:59 +0100 Subject: [PATCH 028/112] IncDec --- testdata/src/default_config/assign/assign.go | 9 ++++ .../default_config/assign/assign.go.golden | 8 ++++ wsl.go | 45 +++++++++++++------ 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/testdata/src/default_config/assign/assign.go b/testdata/src/default_config/assign/assign.go index f70cee2..40b975a 100644 --- a/testdata/src/default_config/assign/assign.go +++ b/testdata/src/default_config/assign/assign.go @@ -10,3 +10,12 @@ func strictAppend() { s = append(s, "d") // want "missing whitespace decreases readability" s = append(s, y) } + +func incDec() { + x := 1 + x++ + x-- + y := x + + _ = y +} diff --git a/testdata/src/default_config/assign/assign.go.golden b/testdata/src/default_config/assign/assign.go.golden index a3476c5..b8421a8 100644 --- a/testdata/src/default_config/assign/assign.go.golden +++ b/testdata/src/default_config/assign/assign.go.golden @@ -12,3 +12,11 @@ func strictAppend() { s = append(s, y) } +func incDec() { + x := 1 + x++ + x-- + y := x + + _ = y +} diff --git a/wsl.go b/wsl.go index 4f7b928..86fe599 100644 --- a/wsl.go +++ b/wsl.go @@ -134,11 +134,12 @@ func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements _, prevIsAssign := previousNode.(*ast.AssignStmt) _, prevIsDecl := previousNode.(*ast.DeclStmt) + _, prevIsIncDec := previousNode.(*ast.IncDecStmt) _, currIsDefer := stmt.(*ast.DeferStmt) // We're cuddled but not with an assign, declare or defer statement which is // never allowed. - if !prevIsAssign && !prevIsDecl && !currIsDefer { + if !prevIsAssign && !prevIsDecl && !currIsDefer && !prevIsIncDec { w.addError(cursor.Stmt().Pos(), cursor.Stmt().Pos(), cursor.Stmt().Pos(), MessageAddWhitespace) return } @@ -172,6 +173,25 @@ func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements } } +func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { + previousNode := cursor.PreviousNode() + + _, prevIsAssign := previousNode.(*ast.AssignStmt) + _, prevIsDecl := previousNode.(*ast.DeclStmt) + _, prevIsIncDec := previousNode.(*ast.IncDecStmt) + + prevIsValidType := previousNode == nil || prevIsAssign || prevIsDecl || prevIsIncDec + + if w.numberOfStatementsAbove(cursor) > 0 && !prevIsValidType { + w.addError( + stmt.Pos(), + stmt.Pos(), + stmt.Pos(), + MessageAddWhitespace, + ) + } +} + func (w *WSL) checkError( stmtsAbove int, ifStmt ast.Node, @@ -432,27 +452,23 @@ func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { return } - previousNode := cursor.PreviousNode() - _, prevIsAssign := previousNode.(*ast.AssignStmt) - _, prevIsDecl := previousNode.(*ast.DeclStmt) + w.CheckCuddlingWithoutIntersection(stmt, cursor) + w.strictAppendCheck(stmt, cursor) +} - if w.numberOfStatementsAbove(cursor) > 0 && previousNode != nil && !prevIsAssign && !prevIsDecl { - w.addError( - stmt.Pos(), - stmt.Pos(), - stmt.Pos(), - MessageAddWhitespace, - ) - } +func (w *WSL) CheckIncDec(stmt *ast.IncDecStmt, cursor *Cursor) { + defer w.CheckExpr(stmt.X, cursor) - w.strictAppendCheck(stmt, previousNode) + w.CheckCuddlingWithoutIntersection(stmt, cursor) } -func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, previousNode ast.Node) { +func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { if _, ok := w.Config.Checks[CheckAppend]; !ok { return } + previousNode := cursor.PreviousNode() + var appendNode *ast.CallExpr for _, expr := range stmt.Rhs { e, ok := expr.(*ast.CallExpr) @@ -529,6 +545,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { w.CheckAssign(s, cursor) // a++ / a-- case *ast.IncDecStmt: + w.CheckIncDec(s, cursor) // defer func() {} case *ast.DeferStmt: w.CheckDefer(s, cursor) From f70204c361c10863b7ecd7aa7fe16612ab4de583 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 6 Jan 2025 22:34:24 +0100 Subject: [PATCH 029/112] Assign and call (not really...) cuddle --- testdata/src/default_config/assign/assign.go | 20 +++++++++++++++++++ .../default_config/assign/assign.go.golden | 20 +++++++++++++++++++ wsl.go | 18 +++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/testdata/src/default_config/assign/assign.go b/testdata/src/default_config/assign/assign.go index 40b975a..c409cab 100644 --- a/testdata/src/default_config/assign/assign.go +++ b/testdata/src/default_config/assign/assign.go @@ -1,5 +1,17 @@ package testpkg +type T struct { + I int +} + +func NewT() *T { + return &T{} +} + +func (*T) Fn() int { + return 1 +} + func strictAppend() { s := []string{} s = append(s, "a") @@ -19,3 +31,11 @@ func incDec() { _ = y } + +func assignAndCall() { + t1 := NewT() + t2 := NewT() + + t1.Fn() + t2.I = t1.Fn() +} diff --git a/testdata/src/default_config/assign/assign.go.golden b/testdata/src/default_config/assign/assign.go.golden index b8421a8..1239be3 100644 --- a/testdata/src/default_config/assign/assign.go.golden +++ b/testdata/src/default_config/assign/assign.go.golden @@ -1,5 +1,17 @@ package testpkg +type T struct { + I int +} + +func NewT() *T { + return &T{} +} + +func (*T) Fn() int { + return 1 +} + func strictAppend() { s := []string{} s = append(s, "a") @@ -20,3 +32,11 @@ func incDec() { _ = y } + +func assignAndCall() { + t1 := NewT() + t2 := NewT() + + t1.Fn() + t2.I = t1.Fn() +} diff --git a/wsl.go b/wsl.go index 86fe599..3c9b534 100644 --- a/wsl.go +++ b/wsl.go @@ -179,9 +179,27 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { _, prevIsAssign := previousNode.(*ast.AssignStmt) _, prevIsDecl := previousNode.(*ast.DeclStmt) _, prevIsIncDec := previousNode.(*ast.IncDecStmt) + _, currIsAssign := stmt.(*ast.AssignStmt) prevIsValidType := previousNode == nil || prevIsAssign || prevIsDecl || prevIsIncDec + // TODO: This is `allow-assign-and-call`, should we deprecate it? + // ref: https://github.com/bombsimon/wsl/blob/52299dcd5c1c2a8baf77b4be4508937486d43656/wsl.go#L559-L563 + // 1. It's not actually checking call - just that we have intersections + // 2. It's a bit too niche I think, either we support assign and call (or + // whatever) or we don't. + // 3. With the new check config, one could just disable checks for assign + // and it would allow cuddling with anything. + if !prevIsValidType && currIsAssign { + currentIdents := allIdents(stmt) + previousIdents := allIdents(previousNode) + intersects := identIntersection(currentIdents, previousIdents) + + if len(intersects) > 0 { + return + } + } + if w.numberOfStatementsAbove(cursor) > 0 && !prevIsValidType { w.addError( stmt.Pos(), From e6d79b5431dd1e013ca0eba2bd61767659509436 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 6 Jan 2025 22:52:45 +0100 Subject: [PATCH 030/112] Improved decl --- testdata/src/default_config/decl/decl.go | 5 +++ .../src/default_config/decl/decl.go.golden | 6 +++ wsl.go | 37 +++++++++++++------ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/testdata/src/default_config/decl/decl.go b/testdata/src/default_config/decl/decl.go index 0402968..9f30b37 100644 --- a/testdata/src/default_config/decl/decl.go +++ b/testdata/src/default_config/decl/decl.go @@ -10,3 +10,8 @@ func fn1() { _ = a _ = b } + +func fn2() { + var a = 1 + var b = a // want "missing whitespace decreases readability" +} diff --git a/testdata/src/default_config/decl/decl.go.golden b/testdata/src/default_config/decl/decl.go.golden index a778706..8402723 100644 --- a/testdata/src/default_config/decl/decl.go.golden +++ b/testdata/src/default_config/decl/decl.go.golden @@ -12,3 +12,9 @@ func fn1() { _ = a _ = b } + +func fn2() { + var a = 1 + + var b = a // want "missing whitespace decreases readability" +} diff --git a/wsl.go b/wsl.go index 3c9b534..1c8b3e1 100644 --- a/wsl.go +++ b/wsl.go @@ -108,8 +108,20 @@ func (w *WSL) Run() { } func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { - reset := cursor.Save() - defer reset() + w.checkCuddlingWithDecl(stmt, cursor, maxAllowedStatements, true) +} + +func (w *WSL) CheckCuddlingNoDecl(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { + w.checkCuddlingWithDecl(stmt, cursor, maxAllowedStatements, false) +} + +func (w *WSL) checkCuddlingWithDecl( + stmt ast.Node, + cursor *Cursor, + maxAllowedStatements int, + declIsValid bool, +) { + defer cursor.Save()() currentIdents := allIdents(cursor.Stmt()) previousIdents := []*ast.Ident{} @@ -137,6 +149,10 @@ func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements _, prevIsIncDec := previousNode.(*ast.IncDecStmt) _, currIsDefer := stmt.(*ast.DeferStmt) + if !declIsValid { + prevIsDecl = false + } + // We're cuddled but not with an assign, declare or defer statement which is // never allowed. if !prevIsAssign && !prevIsDecl && !currIsDefer && !prevIsIncDec { @@ -402,12 +418,7 @@ func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { return } - w.addError( - stmt.Pos(), - stmt.Pos(), - stmt.Pos(), - MessageAddWhitespace, - ) + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) } func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { @@ -419,7 +430,7 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { return } - w.CheckCuddling(stmt, cursor, 1) + w.CheckCuddlingNoDecl(stmt, cursor, 1) } func (w *WSL) CheckBlock(block *ast.BlockStmt) { @@ -428,7 +439,6 @@ func (w *WSL) CheckBlock(block *ast.BlockStmt) { cursor := NewCursor(-1, block.List) for cursor.Next() { - // fmt.Printf("%d: %T\n", cursor.currentIdx, cursor.Stmt()) w.CheckStmt(cursor.Stmt(), cursor) } } @@ -603,8 +613,7 @@ func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) { // numberOfStatementsAbove will find out how many lines above the cursor's // current statement there is without any newlines between. func (w *WSL) numberOfStatementsAbove(cursor *Cursor) int { - reset := cursor.Save() - defer reset() + defer cursor.Save()() statementsWithoutNewlines := 0 currentStmtStartLine := w.lineFor(cursor.Stmt().Pos()) @@ -785,6 +794,10 @@ func allIdents(node ast.Node) []*ast.Ident { for _, name := range n.Names { idents = append(idents, allIdents(name)...) } + + for _, value := range n.Values { + idents = append(idents, allIdents(value)...) + } case *ast.AssignStmt: // TODO: For TypeSwitchStatements, this can be a false positive by // allowing shadowing and "tricking" usage; From 2950204fa39080422abdfb22dacde1871e97b7c4 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 7 Jan 2025 20:02:47 +0100 Subject: [PATCH 031/112] More decl testing --- testdata/src/default_config/decl/decl.go | 9 ++++++ .../src/default_config/decl/decl.go.golden | 11 ++++++++ .../no_check_decl/no_check_decl.go | 7 +++++ .../no_check_decl/no_check_decl.go.golden | 8 ++++++ wsl.go | 28 +++++++++++++++++++ 5 files changed, 63 insertions(+) diff --git a/testdata/src/default_config/decl/decl.go b/testdata/src/default_config/decl/decl.go index 9f30b37..2554f68 100644 --- a/testdata/src/default_config/decl/decl.go +++ b/testdata/src/default_config/decl/decl.go @@ -7,8 +7,17 @@ func fn1() { const c = 3 const d = 4 // want "missing whitespace decreases readability" + e := 5 + var f = 6 // want "missing whitespace decreases readability" + g := 7 // want "missing whitespace decreases readability" + _ = a _ = b + _ = c + _ = d + _ = e + _ = f + _ = g } func fn2() { diff --git a/testdata/src/default_config/decl/decl.go.golden b/testdata/src/default_config/decl/decl.go.golden index 8402723..67d0b8b 100644 --- a/testdata/src/default_config/decl/decl.go.golden +++ b/testdata/src/default_config/decl/decl.go.golden @@ -9,8 +9,19 @@ func fn1() { const d = 4 // want "missing whitespace decreases readability" + e := 5 + + var f = 6 // want "missing whitespace decreases readability" + + g := 7 // want "missing whitespace decreases readability" + _ = a _ = b + _ = c + _ = d + _ = e + _ = f + _ = g } func fn2() { diff --git a/testdata/src/with_config/no_check_decl/no_check_decl.go b/testdata/src/with_config/no_check_decl/no_check_decl.go index 6e9334d..2036697 100644 --- a/testdata/src/with_config/no_check_decl/no_check_decl.go +++ b/testdata/src/with_config/no_check_decl/no_check_decl.go @@ -6,8 +6,15 @@ func fn1() { const c = 3 const d = 4 + e := 5 + var f = 6 + g := 7 + _ = a _ = b _ = c _ = d + _ = e + _ = f + _ = g } diff --git a/testdata/src/with_config/no_check_decl/no_check_decl.go.golden b/testdata/src/with_config/no_check_decl/no_check_decl.go.golden index 7a8de41..c1758b9 100644 --- a/testdata/src/with_config/no_check_decl/no_check_decl.go.golden +++ b/testdata/src/with_config/no_check_decl/no_check_decl.go.golden @@ -6,9 +6,17 @@ func fn1() { const c = 3 const d = 4 + + e := 5 + var f = 6 + g := 7 + _ = a _ = b _ = c _ = d + _ = e + _ = f + _ = g } diff --git a/wsl.go b/wsl.go index 1c8b3e1..9c902ac 100644 --- a/wsl.go +++ b/wsl.go @@ -42,6 +42,24 @@ const ( CheckTypeSwitch ) +/* +Configuration migration + +- StrictAppend | Deprecate. Replaced with `CheckAppend` +- AllowAssignAndCallCuddle | TBD deprecate. Implemented, not configurable +- AllowAssignAndAnythingCuddle | Deprecated. Replaced with `CheckAssign` +- AllowMultiLineAssignCuddle | Deprecate. +- ForceCaseTrailingWhitespaceLimit | TODO +- AllowTrailingComment | TBD deprecate. Should be seen same as leading (allowed) +- AllowSeparatedLeadingComment | Deprecate. Always allowed. +- AllowCuddleDeclaration | Deprecate. Use `CheckDecl` instead. +- AllowCuddleWithCalls | TBD deprecate. Should not be needed. Was added to support mutex unlocking +- AllowCuddleWithRHS | TBD deprecate. Should not be needed. Not clear why separate from above +- ForceCuddleErrCheckAndAssign | Deprecate. Replaced with `CheckErr` +- ErrorVariableNames | Deprecate. We're now looking if the variable implements the error interface +- ForceExclusiveShortDeclarations | TODO +- IncludeGenerated | TODO +*/ type Configuration struct { Checks map[CheckType]struct{} } @@ -149,6 +167,9 @@ func (w *WSL) checkCuddlingWithDecl( _, prevIsIncDec := previousNode.(*ast.IncDecStmt) _, currIsDefer := stmt.(*ast.DeferStmt) + // Most of the time we allow cuddling with declarations (var) if it's just + // one statement but not always so this can be disabled, e.g. for + // delclarations themselves. if !declIsValid { prevIsDecl = false } @@ -197,6 +218,13 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { _, prevIsIncDec := previousNode.(*ast.IncDecStmt) _, currIsAssign := stmt.(*ast.AssignStmt) + // Most of the time we allow cuddling with declarations (var) if it's just + // one statement but not always so this can be disabled, e.g. for + // delclarations themselves. + if _, ok := w.Config.Checks[CheckDecl]; ok { + prevIsDecl = false + } + prevIsValidType := previousNode == nil || prevIsAssign || prevIsDecl || prevIsIncDec // TODO: This is `allow-assign-and-call`, should we deprecate it? From 51074f90787c55aaabec4b051213b937fe8728e9 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 7 Jan 2025 20:56:08 +0100 Subject: [PATCH 032/112] Some config work --- analyzer.go | 83 ++++++++++++++++++++++- config.go | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++ wsl.go | 117 +++------------------------------ wsl_test.go | 4 +- 4 files changed, 276 insertions(+), 113 deletions(-) create mode 100644 config.go diff --git a/analyzer.go b/analyzer.go index ae06fce..9e278dd 100644 --- a/analyzer.go +++ b/analyzer.go @@ -2,6 +2,8 @@ package wsl import ( "flag" + "go/ast" + "go/token" "strings" "golang.org/x/tools/go/analysis" @@ -22,23 +24,60 @@ func NewAnalyzer(config *Configuration) *analysis.Analyzer { // wslAnalyzer is a wrapper around the configuration which is used to be able to // set the configuration when creating the analyzer and later be able to update // flags and running method. +// TODO: This should be a public configuration you can pass to `NewAnalyzer` to +// enable `goalngci-lint` to pass `includeGenerated` but also pass a simpler +// enable/disable list of strings. type wslAnalyzer struct { - config *Configuration + config *Configuration + includeGenerated bool + enableAll bool + disableAll bool + enable []string + disable []string } func (wa *wslAnalyzer) flags() flag.FlagSet { flags := flag.NewFlagSet("", flag.ExitOnError) + // If we have a configuration set we're not running from the command line so + // we don't use any flags. + if wa.config != nil { + return *flags + } + + wa.config = NewConfig() + + flags.BoolVar(&wa.includeGenerated, "include-generated", false, "Include generated files") + flags.BoolVar(&wa.enableAll, "enable-all", false, "Enable all checks") + flags.BoolVar(&wa.disableAll, "disable-all", false, "Disable all checks") + flags.Var(&multiStringValue{slicePtr: &wa.enable}, "enable", "Comma separated list of checks to enable") + flags.Var(&multiStringValue{slicePtr: &wa.disable}, "disable", "Comma separated list of checks to disable") + return *flags } func (wa *wslAnalyzer) run(pass *analysis.Pass) (interface{}, error) { + if err := wa.config.Update(wa.enableAll, wa.disableAll, wa.enable, wa.disable); err != nil { + return nil, err + } + for _, file := range pass.Files { - filename := pass.Fset.Position(file.Pos()).Filename + filename := getFilename(pass.Fset, file) if !strings.HasSuffix(filename, ".go") { continue } + // if the file is related to cgo the filename of the unadjusted position + // is a not a '.go' file. + unadjustedFilename := pass.Fset.PositionFor(file.Pos(), false).Filename + + // The file is skipped if the "unadjusted" file is a Go file, and it's a + // generated file (ex: "_test.go" file). The other non-Go files are + // skipped by the first 'if' with the adjusted position. + if !wa.includeGenerated && ast.IsGenerated(file) && strings.HasSuffix(unadjustedFilename, ".go") { + continue + } + wsl := New(file, pass, wa.config) wsl.Run() @@ -69,3 +108,43 @@ func (wa *wslAnalyzer) run(pass *analysis.Pass) (interface{}, error) { //nolint:nilnil // A pass don't need to return anything. return nil, nil } + +// multiStringValue is a flag that supports multiple values. It's implemented to +// contain a pointer to a string slice that will be overwritten when the flag's +// `Set` method is called. +type multiStringValue struct { + slicePtr *[]string +} + +// Set implements the flag.Value interface and will overwrite the pointer to +// the +// slice with a new pointer after splitting the flag by comma. +func (m *multiStringValue) Set(value string) error { + var s []string + + for _, v := range strings.Split(value, ",") { + s = append(s, strings.TrimSpace(v)) + } + + *m.slicePtr = s + + return nil +} + +// Set implements the flag.Value interface. +func (m *multiStringValue) String() string { + if m.slicePtr == nil { + return "" + } + + return strings.Join(*m.slicePtr, ", ") +} + +func getFilename(fset *token.FileSet, file *ast.File) string { + filename := fset.PositionFor(file.Pos(), true).Filename + if !strings.HasSuffix(filename, ".go") { + return fset.PositionFor(file.Pos(), false).Filename + } + + return filename +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..2a29f1f --- /dev/null +++ b/config.go @@ -0,0 +1,185 @@ +package wsl + +import ( + "fmt" + "strings" +) + +// CheckSet is a set of checks to run. +type CheckSet map[CheckType]struct{} + +// CheckType is a type that represents a checker to run. +type CheckType int + +// Each checker is represented by a CheckType that is used to enable or disable +// the check. +const ( + CheckInvalid CheckType = iota + CheckAssign + CheckAppend + CheckBreak + CheckCase + CheckContinue + CheckDecl + CheckDefer + CheckExpr + CheckErr + CheckFor + CheckGo + CheckIf + CheckLeadingWhitespace + CheckTrailingWhitespace + CheckRange + CheckReturn + CheckSwitch + CheckTypeSwitch +) + +/* +Configuration migration + +- StrictAppend | Deprecate. Replaced with `CheckAppend` +- AllowAssignAndCallCuddle | TBD deprecate. Implemented, not configurable +- AllowAssignAndAnythingCuddle | Deprecated. Replaced with `CheckAssign` +- AllowMultiLineAssignCuddle | Deprecate. +- ForceCaseTrailingWhitespaceLimit | TODO +- AllowTrailingComment | TBD deprecate. Should be seen same as leading (allowed) +- AllowSeparatedLeadingComment | Deprecate. Always allowed. +- AllowCuddleDeclaration | Deprecate. Use `CheckDecl` instead. +- AllowCuddleWithCalls | TBD deprecate. Should not be needed. Was added to support mutex unlocking +- AllowCuddleWithRHS | TBD deprecate. Should not be needed. Not clear why separate from above +- ForceCuddleErrCheckAndAssign | Deprecate. Replaced with `CheckErr` +- ErrorVariableNames | Deprecate. We're now looking if the variable implements the error interface +- ForceExclusiveShortDeclarations | TODO +- IncludeGenerated | Done. +*/ +type Configuration struct { + Checks CheckSet +} + +func NewConfig() *Configuration { + return &Configuration{ + Checks: DefaultChecks(), + } +} + +func (c *Configuration) Update( + enableAll bool, + disableAll bool, + enable []string, + disable []string, +) error { + if enableAll && disableAll { + return fmt.Errorf("can't use both `enable-all` and `disable-all`") + } + + if enableAll { + c.Checks = AllChecks() + } + + if disableAll { + c.Checks = NoChecks() + } + + for _, s := range enable { + check, err := CheckFromString(s) + if err != nil { + return fmt.Errorf("invalid check '%s'", s) + } + + c.Checks.Add(check) + } + + for _, s := range disable { + check, err := CheckFromString(s) + if err != nil { + return fmt.Errorf("invalid check '%s'", s) + } + + c.Checks.Remove(check) + } + + return nil +} + +func DefaultChecks() CheckSet { + return CheckSet{ + CheckAssign: {}, + CheckAppend: {}, + CheckBreak: {}, + CheckCase: {}, + CheckContinue: {}, + CheckDecl: {}, + CheckDefer: {}, + CheckExpr: {}, + CheckFor: {}, + CheckGo: {}, + CheckIf: {}, + CheckLeadingWhitespace: {}, + CheckTrailingWhitespace: {}, + CheckRange: {}, + CheckReturn: {}, + CheckSwitch: {}, + CheckTypeSwitch: {}, + } +} + +func AllChecks() CheckSet { + c := DefaultChecks() + c.Add(CheckErr) + + return c +} + +func NoChecks() CheckSet { + return CheckSet{} +} + +func (c CheckSet) Add(check CheckType) { + c[check] = struct{}{} +} + +func (c CheckSet) Remove(check CheckType) { + delete(c, check) +} + +func CheckFromString(s string) (CheckType, error) { + switch strings.ToLower(s) { + case "assign": + return CheckAssign, nil + case "append": + return CheckAppend, nil + case "break": + return CheckBreak, nil + case "case": + return CheckCase, nil + case "continue": + return CheckContinue, nil + case "decl": + return CheckDecl, nil + case "defer": + return CheckDefer, nil + case "expr": + return CheckExpr, nil + case "for": + return CheckFor, nil + case "go": + return CheckGo, nil + case "if": + return CheckIf, nil + case "leading-whitespace": + return CheckLeadingWhitespace, nil + case "trailing-whitespace": + return CheckTrailingWhitespace, nil + case "range": + return CheckRange, nil + case "return": + return CheckReturn, nil + case "switch": + return CheckSwitch, nil + case "type-switch": + return CheckTypeSwitch, nil + default: + return CheckInvalid, fmt.Errorf("invalid check '%s'", s) + } +} diff --git a/wsl.go b/wsl.go index 9c902ac..2a74fea 100644 --- a/wsl.go +++ b/wsl.go @@ -16,78 +16,6 @@ const ( MessageRemoveWhitespace = "unnecessary whitespace decreases readability" ) -// CheckType is a type that represents a checker to run. -type CheckType int - -// Each checker is represented by a CheckType that is used to enable or disable -// the check. -const ( - CheckAssign CheckType = iota - CheckAppend - CheckBreak - CheckCase - CheckContinue - CheckDecl - CheckDefer - CheckExpr - CheckErr - CheckFor - CheckGo - CheckIf - CheckLeadingWhitespace - CheckTrailingWhitespace - CheckRange - CheckReturn - CheckSwitch - CheckTypeSwitch -) - -/* -Configuration migration - -- StrictAppend | Deprecate. Replaced with `CheckAppend` -- AllowAssignAndCallCuddle | TBD deprecate. Implemented, not configurable -- AllowAssignAndAnythingCuddle | Deprecated. Replaced with `CheckAssign` -- AllowMultiLineAssignCuddle | Deprecate. -- ForceCaseTrailingWhitespaceLimit | TODO -- AllowTrailingComment | TBD deprecate. Should be seen same as leading (allowed) -- AllowSeparatedLeadingComment | Deprecate. Always allowed. -- AllowCuddleDeclaration | Deprecate. Use `CheckDecl` instead. -- AllowCuddleWithCalls | TBD deprecate. Should not be needed. Was added to support mutex unlocking -- AllowCuddleWithRHS | TBD deprecate. Should not be needed. Not clear why separate from above -- ForceCuddleErrCheckAndAssign | Deprecate. Replaced with `CheckErr` -- ErrorVariableNames | Deprecate. We're now looking if the variable implements the error interface -- ForceExclusiveShortDeclarations | TODO -- IncludeGenerated | TODO -*/ -type Configuration struct { - Checks map[CheckType]struct{} -} - -func NewConfig() *Configuration { - return &Configuration{ - Checks: map[CheckType]struct{}{ - CheckAssign: {}, - CheckAppend: {}, - CheckBreak: {}, - CheckCase: {}, - CheckContinue: {}, - CheckDecl: {}, - CheckDefer: {}, - CheckExpr: {}, - CheckFor: {}, - CheckGo: {}, - CheckIf: {}, - CheckLeadingWhitespace: {}, - CheckTrailingWhitespace: {}, - CheckRange: {}, - CheckReturn: {}, - CheckSwitch: {}, - CheckTypeSwitch: {}, - }, - } -} - type FixRange struct { FixRangeStart token.Pos FixRangeEnd token.Pos @@ -200,12 +128,7 @@ func (w *WSL) checkCuddlingWithDecl( // Idents on the line above exist in the current condition so that // should remain cuddled. if len(intersects) > 0 { - w.addError( - previousNode.Pos(), - previousNode.Pos(), - previousNode.Pos(), - MessageAddWhitespace, - ) + w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) } } } @@ -227,7 +150,8 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { prevIsValidType := previousNode == nil || prevIsAssign || prevIsDecl || prevIsIncDec - // TODO: This is `allow-assign-and-call`, should we deprecate it? + // TODO: This is `allow-assign-and-call`/`AllowAssignAndCallCuddle`, should + // we deprecate it? // ref: https://github.com/bombsimon/wsl/blob/52299dcd5c1c2a8baf77b4be4508937486d43656/wsl.go#L559-L563 // 1. It's not actually checking call - just that we have intersections // 2. It's a bit too niche I think, either we support assign and call (or @@ -245,12 +169,7 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { } if w.numberOfStatementsAbove(cursor) > 0 && !prevIsValidType { - w.addError( - stmt.Pos(), - stmt.Pos(), - stmt.Pos(), - MessageAddWhitespace, - ) + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) } } @@ -279,12 +198,7 @@ func (w *WSL) checkError( return } - w.addError( - ifStmt.Pos(), - previousNode.End(), - ifStmt.Pos(), - MessageRemoveWhitespace, - ) + w.addError(ifStmt.Pos(), previousNode.End(), ifStmt.Pos(), MessageRemoveWhitespace) // If we add the error at the same position but with a different fix // range, only the fix range will be updated. @@ -303,12 +217,7 @@ func (w *WSL) checkError( cursor.Previous() if w.numberOfStatementsAbove(cursor) > 0 { - w.addError( - ifStmt.Pos(), - previousNode.Pos(), - previousNode.Pos(), - MessageAddWhitespace, - ) + w.addError(ifStmt.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) } } @@ -489,12 +398,7 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { return } - w.addError( - stmt.Pos(), - stmt.Pos(), - stmt.Pos(), - MessageAddWhitespace, - ) + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) } func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { @@ -547,12 +451,7 @@ func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { intersects := identIntersection(appendIdents, previousIdents) if len(intersects) == 0 { - w.addError( - stmt.Pos(), - stmt.Pos(), - stmt.Pos(), - MessageAddWhitespace, - ) + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) } } diff --git a/wsl_test.go b/wsl_test.go index f0f6efb..e735773 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -34,13 +34,13 @@ func TestWithConfig(t *testing.T) { { subdir: "if_errcheck", configFn: func(config *Configuration) { - config.Checks[CheckErr] = struct{}{} + config.Checks.Add(CheckErr) }, }, { subdir: "no_check_decl", configFn: func(config *Configuration) { - delete(config.Checks, CheckDecl) + config.Checks.Remove(CheckDecl) }, }, } { From b5e8e093c05dee6971058701c169e12dac7db945 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 9 Jan 2025 21:54:36 +0100 Subject: [PATCH 033/112] Support to cuddling if used anywhere in block --- config.go | 4 + cursor.go | 12 ++- cursor_test.go | 10 +-- .../no_check_decl/no_check_decl.go.golden | 33 ++++---- testdata/src/with_config/whole_block/block.go | 39 ++++++++++ .../with_config/whole_block/block.go.golden | 41 ++++++++++ wsl.go | 75 +++++++++++++++---- wsl_test.go | 6 ++ 8 files changed, 181 insertions(+), 39 deletions(-) create mode 100644 testdata/src/with_config/whole_block/block.go create mode 100644 testdata/src/with_config/whole_block/block.go.golden diff --git a/config.go b/config.go index 2a29f1f..069743e 100644 --- a/config.go +++ b/config.go @@ -33,6 +33,7 @@ const ( CheckReturn CheckSwitch CheckTypeSwitch + CheckWholeBlock ) /* @@ -127,6 +128,7 @@ func DefaultChecks() CheckSet { func AllChecks() CheckSet { c := DefaultChecks() c.Add(CheckErr) + c.Add(CheckWholeBlock) return c } @@ -179,6 +181,8 @@ func CheckFromString(s string) (CheckType, error) { return CheckSwitch, nil case "type-switch": return CheckTypeSwitch, nil + case "whole-block": + return CheckWholeBlock, nil default: return CheckInvalid, fmt.Errorf("invalid check '%s'", s) } diff --git a/cursor.go b/cursor.go index 6050396..0549b3d 100644 --- a/cursor.go +++ b/cursor.go @@ -10,12 +10,20 @@ var ErrCursourOutObFounds = errors.New("out of bounds") type Cursor struct { currentIdx int statements []ast.Stmt + idents map[string]struct{} } -func NewCursor(i int, statements []ast.Stmt) *Cursor { +func NewCursor(statements []ast.Stmt) *Cursor { return &Cursor{ - currentIdx: i, + currentIdx: -1, statements: statements, + idents: make(map[string]struct{}), + } +} + +func (c *Cursor) AddIdents(idents []*ast.Ident) { + for _, i := range idents { + c.idents[i.Name] = struct{}{} } } diff --git a/cursor_test.go b/cursor_test.go index b15d9a7..9f5539d 100644 --- a/cursor_test.go +++ b/cursor_test.go @@ -8,7 +8,7 @@ import ( ) func TestSavestate(t *testing.T) { - cursor := NewCursor(0, []ast.Stmt{ + cursor := NewCursor([]ast.Stmt{ &ast.AssignStmt{}, &ast.AssignStmt{}, &ast.AssignStmt{}, @@ -36,14 +36,14 @@ func TestSavestate(t *testing.T) { cursor.Next() cursor.Next() - assert.Equal(t, 6, cursor.currentIdx) + assert.Equal(t, 5, cursor.currentIdx) }() - assert.Equal(t, 4, cursor.currentIdx) + assert.Equal(t, 3, cursor.currentIdx) }() - assert.Equal(t, 2, cursor.currentIdx) + assert.Equal(t, 1, cursor.currentIdx) reset() - assert.Equal(t, 0, cursor.currentIdx) + assert.Equal(t, -1, cursor.currentIdx) } diff --git a/testdata/src/with_config/no_check_decl/no_check_decl.go.golden b/testdata/src/with_config/no_check_decl/no_check_decl.go.golden index c1758b9..330adfe 100644 --- a/testdata/src/with_config/no_check_decl/no_check_decl.go.golden +++ b/testdata/src/with_config/no_check_decl/no_check_decl.go.golden @@ -1,22 +1,23 @@ package testpkg -func fn1() { - var a = 1 - var b = 2 - const c = 3 - const d = 4 +import "fmt" + +func Fn() {} +func fn1() { + a := 1 + if true { + _ = 1 + fmt.Println(a) + } - e := 5 - var f = 6 - g := 7 + b := 2 + for range make([]int, 10) { + _ = 1 + if false { + Fn() - _ = a - _ = b - _ = c - _ = d - _ = e - _ = f - _ = g + Fn(b) + } + } } - diff --git a/testdata/src/with_config/whole_block/block.go b/testdata/src/with_config/whole_block/block.go new file mode 100644 index 0000000..d8fd6b2 --- /dev/null +++ b/testdata/src/with_config/whole_block/block.go @@ -0,0 +1,39 @@ +package testpkg + +func Fn(_ int) {} + +func fn1() { + a := 1 + if true { + if false { + b := 2 + if true { // want "missing whitespace decreases readability" + Fn(a) + } + + _ = b + } + } + + y := 2 + if true { // want "missing whitespace decreases readability" + if false { + a = 1 + if true { + Fn(a) + } + } + } + + i := 0 + if true { + _ = 1 + _ = 2 + _ = 3 + + i++ + } + + _ = x + _ = y +} diff --git a/testdata/src/with_config/whole_block/block.go.golden b/testdata/src/with_config/whole_block/block.go.golden new file mode 100644 index 0000000..b849b59 --- /dev/null +++ b/testdata/src/with_config/whole_block/block.go.golden @@ -0,0 +1,41 @@ +package testpkg + +func Fn(_ int) {} + +func fn1() { + a := 1 + if true { + if false { + b := 2 + + if true { // want "missing whitespace decreases readability" + Fn(a) + } + + _ = b + } + } + + y := 2 + + if true { // want "missing whitespace decreases readability" + if false { + a = 1 + if true { + Fn(a) + } + } + } + + i := 0 + if true { + _ = 1 + _ = 2 + _ = 3 + + i++ + } + + _ = x + _ = y +} diff --git a/wsl.go b/wsl.go index 2a74fea..81e9b31 100644 --- a/wsl.go +++ b/wsl.go @@ -111,7 +111,21 @@ func (w *WSL) checkCuddlingWithDecl( // TODO: Features: // * Allow idents that is used first in the block - // * OR - if configured - anywhere in the block. + + // FEATURE: Enable identifier used anywhere in block. + // The cursor has already parsed the block we're comparing so let's see if + // the ident is used anywhere in the block. + if _, ok := w.Config.Checks[CheckWholeBlock]; ok { + anyIntersects := identsInMap(previousIdents, cursor.idents) + if len(anyIntersects) > 0 { + // fmt.Printf("Valid since we have %v any in the block\n", previousIdents) + if numStmtsAbove > maxAllowedStatements { + w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) + } + + return + } + } // We're cuddled but the line immediately above doesn't contain any // variables used in this statement. @@ -230,12 +244,8 @@ func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { } func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { - if _, ok := w.Config.Checks[CheckIf]; ok { - w.CheckCuddling(stmt, cursor, 1) - } - // if - w.CheckBlock(stmt.Body) + cursor.idents = w.CheckBlock(stmt.Body) switch v := stmt.Else.(type) { // else-if @@ -243,7 +253,15 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { w.CheckIf(v, cursor) // else case *ast.BlockStmt: - w.CheckBlock(v) + elseIdents := w.CheckBlock(v) + + for k, v := range elseIdents { + cursor.idents[k] = v + } + } + + if _, ok := w.Config.Checks[CheckIf]; ok { + w.CheckCuddling(stmt, cursor, 1) } } @@ -258,7 +276,7 @@ func (w *WSL) CheckFor(stmt *ast.ForStmt, cursor *Cursor) { } func (w *WSL) CheckRange(stmt *ast.RangeStmt, cursor *Cursor) { - defer w.CheckBlock(stmt.Body) + cursor.idents = w.CheckBlock(stmt.Body) if _, ok := w.Config.Checks[CheckRange]; !ok { return @@ -268,7 +286,7 @@ func (w *WSL) CheckRange(stmt *ast.RangeStmt, cursor *Cursor) { } func (w *WSL) CheckSwitch(stmt *ast.SwitchStmt, cursor *Cursor) { - defer w.CheckBlock(stmt.Body) + cursor.idents = w.CheckBlock(stmt.Body) if _, ok := w.Config.Checks[CheckSwitch]; !ok { return @@ -278,7 +296,7 @@ func (w *WSL) CheckSwitch(stmt *ast.SwitchStmt, cursor *Cursor) { } func (w *WSL) CheckTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { - defer w.CheckBlock(stmt.Body) + cursor.idents = w.CheckBlock(stmt.Body) if _, ok := w.Config.Checks[CheckTypeSwitch]; !ok { return @@ -370,14 +388,17 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { w.CheckCuddlingNoDecl(stmt, cursor, 1) } -func (w *WSL) CheckBlock(block *ast.BlockStmt) { +func (w *WSL) CheckBlock(block *ast.BlockStmt) map[string]struct{} { w.CheckBlockLeadingNewline(block) w.CheckTrailingNewline(block) - cursor := NewCursor(-1, block.List) + cursor := NewCursor(block.List) for cursor.Next() { w.CheckStmt(cursor.Stmt(), cursor) + cursor.AddIdents(allIdents(cursor.Stmt())) } + + return cursor.idents } func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { @@ -462,7 +483,7 @@ func (w *WSL) CheckCase(stmt *ast.CaseClause, cursor *Cursor) { return } - cursor = NewCursor(-1, stmt.Body) + cursor = NewCursor(stmt.Body) for cursor.Next() { w.CheckStmt(cursor.Stmt(), cursor) } @@ -515,7 +536,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { w.CheckCase(s, cursor) // { } case *ast.BlockStmt: - w.CheckBlock(s) + cursor.idents = w.CheckBlock(s) default: fmt.Printf("Not implemented stmt: %T\n", s) } @@ -525,7 +546,7 @@ func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) { switch s := expr.(type) { // func() {} case *ast.FuncLit: - w.CheckBlock(s.Body) + cursor.idents = w.CheckBlock(s.Body) // Call(args...) case *ast.CallExpr: for _, e := range s.Args { @@ -778,7 +799,17 @@ func allIdents(node ast.Node) []*ast.Ident { for _, elt := range n.Elts { idents = append(idents, allIdents(elt)...) } - case *ast.BasicLit, *ast.FuncLit, *ast.IncDecStmt, *ast.BranchStmt, + case *ast.IncDecStmt: + idents = allIdents(n.X) + case *ast.CaseClause: + for _, expr := range n.List { + idents = append(idents, allIdents(expr)...) + } + case *ast.ReturnStmt: + for _, r := range n.Results { + idents = append(idents, allIdents(r)...) + } + case *ast.BasicLit, *ast.FuncLit, *ast.BranchStmt, *ast.ArrayType: default: spew.Dump(node) @@ -801,3 +832,15 @@ func identIntersection(a, b []*ast.Ident) []*ast.Ident { return intersects } + +func identsInMap(a []*ast.Ident, m map[string]struct{}) []*ast.Ident { + intersects := []*ast.Ident{} + + for _, as := range a { + if _, ok := m[as.Name]; ok { + intersects = append(intersects, as) + } + } + + return intersects +} diff --git a/wsl_test.go b/wsl_test.go index e735773..74cd476 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -43,6 +43,12 @@ func TestWithConfig(t *testing.T) { config.Checks.Remove(CheckDecl) }, }, + { + subdir: "whole_block", + configFn: func(config *Configuration) { + config.Checks.Add(CheckWholeBlock) + }, + }, } { t.Run(tc.subdir, func(t *testing.T) { config := NewConfig() From 52a0e5359251a9e1ba3beb45942ca49234571237 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 11 Jan 2025 18:52:34 +0100 Subject: [PATCH 034/112] Whole block and first statement config --- README.md | 13 +- config.go | 8 +- cursor.go | 90 +++++- testdata/src/with_config/whole_block/block.go | 139 ++++++++-- .../with_config/whole_block/block.go.golden | 143 ++++++++-- wsl.go | 256 ++++++++++-------- 6 files changed, 483 insertions(+), 166 deletions(-) diff --git a/README.md b/README.md index 9b5c0f6..e57e835 100644 --- a/README.md +++ b/README.md @@ -11,25 +11,22 @@ the start and the end of blocks. ## Usage > **Note**: This linter provides a fixer that can fix most issues with the -> `--fix` flag. However, currently `golangci-lint` [does not support suggested -> fixes][suggested-fixes-issue] so the `--fix` -> flag in `golangci-lint` will **not** work. +> `--fix` flag. -`wsl` uses the [analysis] -package meaning it will operate on package level with the default analysis flags -and way of working. +`wsl` uses the [analysis] package meaning it will operate on package level with +the default analysis flags and way of working. ```sh wsl --help wsl [flags] -wsl --allow-cuddle-declarations --fix ./... +wsl --disable-all --enable break,continue --fix ./... ``` `wsl` is also integrated in [`golangci-lint`][golangci-lint] ```sh -golangci-lint run --no-config --disable-all --enable wsl +golangci-lint run --no-config --disable-all --enable wsl --fix ``` ## Issues and configuration diff --git a/config.go b/config.go index 069743e..5eb7415 100644 --- a/config.go +++ b/config.go @@ -18,7 +18,6 @@ const ( CheckAssign CheckAppend CheckBreak - CheckCase CheckContinue CheckDecl CheckDefer @@ -33,6 +32,7 @@ const ( CheckReturn CheckSwitch CheckTypeSwitch + CheckFirstInBlock CheckWholeBlock ) @@ -108,7 +108,6 @@ func DefaultChecks() CheckSet { CheckAssign: {}, CheckAppend: {}, CheckBreak: {}, - CheckCase: {}, CheckContinue: {}, CheckDecl: {}, CheckDefer: {}, @@ -128,6 +127,7 @@ func DefaultChecks() CheckSet { func AllChecks() CheckSet { c := DefaultChecks() c.Add(CheckErr) + c.Add(CheckFirstInBlock) c.Add(CheckWholeBlock) return c @@ -153,8 +153,6 @@ func CheckFromString(s string) (CheckType, error) { return CheckAppend, nil case "break": return CheckBreak, nil - case "case": - return CheckCase, nil case "continue": return CheckContinue, nil case "decl": @@ -183,6 +181,8 @@ func CheckFromString(s string) (CheckType, error) { return CheckTypeSwitch, nil case "whole-block": return CheckWholeBlock, nil + case "first-in-block": + return CheckWholeBlock, nil default: return CheckInvalid, fmt.Errorf("invalid check '%s'", s) } diff --git a/cursor.go b/cursor.go index 0549b3d..189a94d 100644 --- a/cursor.go +++ b/cursor.go @@ -7,24 +7,100 @@ import ( var ErrCursourOutObFounds = errors.New("out of bounds") +// Cursor holds a list of statements and a pointer to where in the list we are. +// Each block gets a new cursor and can be used to check previous or coming +// statements. +// +// A cursor also keeps a set of all identifiers seen in the block and child +// blocks and a separate list of string slices of first identifier in blocks. +// Each element represents the depth of where the identifiers was found. +// +// In this example: +// +// func Fn() { +// if true { +// a, b := 1, 2 +// if false { +// c := 3 +// d := 4 +// } +// } +// } +// +// idents will be a map containing [a, b, c, d] and first identifiers will be +// [[a, b], [c]] type Cursor struct { - currentIdx int - statements []ast.Stmt - idents map[string]struct{} + currentIdx int + statements []ast.Stmt + idents map[string]struct{} + firstIdents [][]*ast.Ident } +// NewCursor creates a new cursor with a given list of statements. func NewCursor(statements []ast.Stmt) *Cursor { return &Cursor{ - currentIdx: -1, - statements: statements, - idents: make(map[string]struct{}), + currentIdx: -1, + statements: statements, + idents: make(map[string]struct{}), + firstIdents: [][]*ast.Ident{}, } } -func (c *Cursor) AddIdents(idents []*ast.Ident) { +// Extend will extend a cursor with another. This is used when returning from a +// check of a nested block to include all scopes recursively. +func (c *Cursor) Extend(otherCursor *Cursor) { + for i := range otherCursor.idents { + c.idents[i] = struct{}{} + } + + c.firstIdents = append(c.firstIdents, otherCursor.firstIdents...) +} + +// Merge a cursor with another. This is used when checking chained blocks such +// as if-else-chains. +func (c *Cursor) Merge(otherCursor *Cursor) { + if len(otherCursor.firstIdents) > 0 { + otherFirstIdents := otherCursor.firstIdents[len(otherCursor.firstIdents)-1] + + if len(c.firstIdents) == 0 { + c.firstIdents = append(c.firstIdents, otherFirstIdents) + } else { + for _, v := range otherFirstIdents { + c.firstIdents[0] = append(c.firstIdents[0], v) + } + } + } + + for v := range otherCursor.idents { + c.idents[v] = struct{}{} + } +} + +// Retain will retain the first idents from a Cursor putting them all at the +// first index. This is used when there are multiple blocks at the same level +// with different first identifiers such as if-else. +func (c *Cursor) Retain() { + if len(c.firstIdents) == 0 { + return + } + + for _, firsts := range c.firstIdents[1:] { + c.firstIdents[0] = append(c.firstIdents[0], firsts...) + } + + c.firstIdents = c.firstIdents[:1] +} + +// AddIdents adds all idents in the list to the set of identifiers. If it's the +// first statement for the cursor first identifiers will be extended. +func (c *Cursor) AddIdents(idents []*ast.Ident, addFirst bool) { for _, i := range idents { c.idents[i.Name] = struct{}{} } + + if addFirst { + c.firstIdents = append(c.firstIdents, idents) + } } func (c *Cursor) Next() bool { diff --git a/testdata/src/with_config/whole_block/block.go b/testdata/src/with_config/whole_block/block.go index d8fd6b2..9eceb10 100644 --- a/testdata/src/with_config/whole_block/block.go +++ b/testdata/src/with_config/whole_block/block.go @@ -3,37 +3,138 @@ package testpkg func Fn(_ int) {} func fn1() { - a := 1 + x := 1 if true { + y, z := 1, 2 if false { - b := 2 + z++ if true { // want "missing whitespace decreases readability" - Fn(a) + y++ + x++ } + } + } +} + +func fn2() { + a := 1 + b := 1 + c := 1 - _ = b + a = 1 + if true { + a++ + } else if false { + b++ + } else { + c++ + } + + b = 1 + if true { + a++ + } else if false { + b++ + } else { + c++ + } + + c = 1 + if true { + a++ + } else if false { + b++ + } else { + c++ + } +} + +func fn3(a, b, c itn) { + a = 1 + for range make([]int, 10) { + if true { + c++ + + Fn(a) } } - y := 2 - if true { // want "missing whitespace decreases readability" - if false { - a = 1 - if true { - Fn(a) - } + b = 1 + for range make([]int, 10) { // want "missing whitespace decreases readability" + if true { + c++ + + Fn(a) } } - i := 0 - if true { - _ = 1 - _ = 2 - _ = 3 + a = 1 + for i := 0; i < 3; i++ { + if true { + c++ + + Fn(a) + } + } + + b = 1 + for i := 0; i < 3; i++ { // want "missing whitespace decreases readability" + if true { + c++ + + Fn(a) + } + } +} + +func fn4(a, b, c int) { + a = 1 + switch { + case true: + c++ + a++ + case false: + c++ + a-- + } - i++ + b = 1 + switch { // want "missing whitespace decreases readability" + case true: + c++ + a++ + case false: + c++ + a-- } +} + +func fn5(a, b, c int) { + a = 1 + go func() { + c++ + a++ + }() + + b = 1 + go func() { // want "missing whitespace decreases readability" + c++ + a++ + }() +} + +func fn6(a, b, c int) { + a = 1 + defer func() { + if true { + c++ + a++ + } + }() - _ = x - _ = y + b = 1 + defer func() { // want "missing whitespace decreases readability" + c++ + a++ + }() } diff --git a/testdata/src/with_config/whole_block/block.go.golden b/testdata/src/with_config/whole_block/block.go.golden index b849b59..7476b50 100644 --- a/testdata/src/with_config/whole_block/block.go.golden +++ b/testdata/src/with_config/whole_block/block.go.golden @@ -3,39 +3,144 @@ package testpkg func Fn(_ int) {} func fn1() { - a := 1 + x := 1 if true { + y, z := 1, 2 if false { - b := 2 + z++ if true { // want "missing whitespace decreases readability" - Fn(a) + y++ + x++ } + } + } +} + +func fn2() { + a := 1 + b := 1 + c := 1 - _ = b + a = 1 + if true { + a++ + } else if false { + b++ + } else { + c++ + } + + b = 1 + if true { + a++ + } else if false { + b++ + } else { + c++ + } + + c = 1 + if true { + a++ + } else if false { + b++ + } else { + c++ + } +} + +func fn3(a, b, c itn) { + a = 1 + for range make([]int, 10) { + if true { + c++ + + Fn(a) } } - y := 2 + b = 1 - if true { // want "missing whitespace decreases readability" - if false { - a = 1 - if true { - Fn(a) - } + for range make([]int, 10) { // want "missing whitespace decreases readability" + if true { + c++ + + Fn(a) } } - i := 0 - if true { - _ = 1 - _ = 2 - _ = 3 + a = 1 + for i := 0; i < 3; i++ { + if true { + c++ - i++ + Fn(a) + } + } + + b = 1 + + for i := 0; i < 3; i++ { // want "missing whitespace decreases readability" + if true { + c++ + + Fn(a) + } + } +} + +func fn4(a, b, c int) { + a = 1 + switch { + case true: + c++ + a++ + case false: + c++ + a-- + } + + b = 1 + + switch { // want "missing whitespace decreases readability" + case true: + c++ + a++ + case false: + c++ + a-- } +} + +func fn5(a, b, c int) { + a = 1 + go func() { + c++ + a++ + }() + + b = 1 + + go func() { // want "missing whitespace decreases readability" + c++ + a++ + }() +} + +func fn6(a, b, c int) { + a = 1 + defer func() { + if true { + c++ + a++ + } + }() + + b = 1 - _ = x - _ = y + defer func() { // want "missing whitespace decreases readability" + c++ + a++ + }() } diff --git a/wsl.go b/wsl.go index 81e9b31..7e184f3 100644 --- a/wsl.go +++ b/wsl.go @@ -53,17 +53,18 @@ func (w *WSL) Run() { } } -func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { - w.checkCuddlingWithDecl(stmt, cursor, maxAllowedStatements, true) +func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, blockCursor *Cursor, maxAllowedStatements int) { + w.checkCuddlingWithDecl(stmt, cursor, blockCursor, maxAllowedStatements, true) } -func (w *WSL) CheckCuddlingNoDecl(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { - w.checkCuddlingWithDecl(stmt, cursor, maxAllowedStatements, false) +func (w *WSL) CheckCuddlingNoDecl(stmt ast.Node, cursor *Cursor, blockCursor *Cursor, maxAllowedStatements int) { + w.checkCuddlingWithDecl(stmt, cursor, blockCursor, maxAllowedStatements, false) } func (w *WSL) checkCuddlingWithDecl( stmt ast.Node, cursor *Cursor, + blockCursor *Cursor, maxAllowedStatements int, declIsValid bool, ) { @@ -109,16 +110,15 @@ func (w *WSL) checkCuddlingWithDecl( return } - // TODO: Features: - // * Allow idents that is used first in the block + _, firstInBlock := w.Config.Checks[CheckFirstInBlock] + _, wholeBlock := w.Config.Checks[CheckWholeBlock] - // FEATURE: Enable identifier used anywhere in block. - // The cursor has already parsed the block we're comparing so let's see if - // the ident is used anywhere in the block. - if _, ok := w.Config.Checks[CheckWholeBlock]; ok { - anyIntersects := identsInMap(previousIdents, cursor.idents) + // FEATURE: Allow identifier used anywhere in block (including recursive + // blocks). + if wholeBlock { + anyIntersects := identsInMap(previousIdents, blockCursor.idents) if len(anyIntersects) > 0 { - // fmt.Printf("Valid since we have %v any in the block\n", previousIdents) + // We have matches, but too many statements above. if numStmtsAbove > maxAllowedStatements { w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) } @@ -127,6 +127,31 @@ func (w *WSL) checkCuddlingWithDecl( } } + // FEATURE: Allow identifiers used first in block. Configurable to allow + // multiple levels. + if firstInBlock { + // TODO: Make configurable + maxDepth := 1 + + for i := 0; i < maxDepth; i++ { + if i > len(blockCursor.firstIdents) { + firstIntersect := identIntersection( + currentIdents, + blockCursor.firstIdents[i], + ) + + if len(firstIntersect) > 0 { + // We have matches, but too many statements above. + if numStmtsAbove > maxAllowedStatements { + w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) + } + + return + } + } + } + } + // We're cuddled but the line immediately above doesn't contain any // variables used in this statement. intersects := identIntersection(currentIdents, previousIdents) @@ -157,7 +182,7 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { // Most of the time we allow cuddling with declarations (var) if it's just // one statement but not always so this can be disabled, e.g. for - // delclarations themselves. + // declarations themselves. if _, ok := w.Config.Checks[CheckDecl]; ok { prevIsDecl = false } @@ -235,6 +260,45 @@ func (w *WSL) checkError( } } +func (w *WSL) CheckBlockAndExtend( + node ast.Node, + blockStmt *ast.BlockStmt, + cursor *Cursor, + check CheckType, +) { + blockCursor := w.CheckBlock(blockStmt) + + if _, ok := w.Config.Checks[check]; ok { + w.CheckCuddling(node, cursor, blockCursor, 1) + } + + cursor.Extend(blockCursor) +} + +func (w *WSL) CheckExprAndExtend( + node ast.Node, + expr ast.Expr, + cursor *Cursor, + predicate func(ast.Node) (int, bool), + check CheckType, +) { + maybeBlockCursor := w.CheckExpr(expr, cursor) + + if _, ok := w.Config.Checks[check]; ok { + previousNode := cursor.PreviousNode() + + // We can cuddle any amount `go` statements so only check cuddling if the + // previous one isn't a `go` call. + // We don't even have to check if it's actually cuddled or just the previous + // one because even if it's not but is a `go` statement it's valid. + if n, ok := predicate(previousNode); ok { + w.CheckCuddling(node, cursor, maybeBlockCursor, n) + } + } + + cursor.Extend(maybeBlockCursor) +} + func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { if funcDecl.Body == nil { return @@ -245,114 +309,81 @@ func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { // if - cursor.idents = w.CheckBlock(stmt.Body) + blockCursor := w.CheckBlock(stmt.Body) switch v := stmt.Else.(type) { // else-if case *ast.IfStmt: - w.CheckIf(v, cursor) + // We use our new cursor created for this if-block to check nested if. + w.CheckIf(v, blockCursor) + blockCursor.Retain() + // else case *ast.BlockStmt: - elseIdents := w.CheckBlock(v) - - for k, v := range elseIdents { - cursor.idents[k] = v - } + // Merge the idents from the else branch. + blockCursor.Merge(w.CheckBlock(v)) } if _, ok := w.Config.Checks[CheckIf]; ok { - w.CheckCuddling(stmt, cursor, 1) + w.CheckCuddling(stmt, cursor, blockCursor, 1) } + + cursor.Extend(blockCursor) } func (w *WSL) CheckFor(stmt *ast.ForStmt, cursor *Cursor) { - defer w.CheckBlock(stmt.Body) - - if _, ok := w.Config.Checks[CheckFor]; !ok { - return - } - - w.CheckCuddling(stmt, cursor, 1) + w.CheckBlockAndExtend(stmt, stmt.Body, cursor, CheckFor) } func (w *WSL) CheckRange(stmt *ast.RangeStmt, cursor *Cursor) { - cursor.idents = w.CheckBlock(stmt.Body) - - if _, ok := w.Config.Checks[CheckRange]; !ok { - return - } - - w.CheckCuddling(stmt, cursor, 1) + w.CheckBlockAndExtend(stmt, stmt.Body, cursor, CheckRange) } func (w *WSL) CheckSwitch(stmt *ast.SwitchStmt, cursor *Cursor) { - cursor.idents = w.CheckBlock(stmt.Body) - - if _, ok := w.Config.Checks[CheckSwitch]; !ok { - return - } - - w.CheckCuddling(stmt, cursor, 1) + w.CheckBlockAndExtend(stmt, stmt.Body, cursor, CheckSwitch) } func (w *WSL) CheckTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { - cursor.idents = w.CheckBlock(stmt.Body) - - if _, ok := w.Config.Checks[CheckTypeSwitch]; !ok { - return - } - - w.CheckCuddling(stmt, cursor, 1) + w.CheckBlockAndExtend(stmt, stmt.Body, cursor, CheckTypeSwitch) } func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { - defer w.CheckExpr(stmt.X, cursor) - - if _, ok := w.Config.Checks[CheckExpr]; !ok { - return - } - - previousNode := cursor.PreviousNode() - - // We can cuddle any amount call statements so only check cuddling if the - // previous one isn't a function call. - if _, ok := previousNode.(*ast.ExprStmt); !ok { - w.CheckCuddling(stmt, cursor, -1) - } + w.CheckExprAndExtend( + stmt, + stmt.X, + cursor, + func(n ast.Node) (int, bool) { + _, ok := n.(*ast.ExprStmt) + return -1, !ok + }, + CheckExpr, + ) } func (w *WSL) CheckGo(stmt *ast.GoStmt, cursor *Cursor) { - defer w.CheckExpr(stmt.Call, cursor) - - if _, ok := w.Config.Checks[CheckGo]; !ok { - return - } - - previousNode := cursor.PreviousNode() - - // We can cuddle any amount `go` statements so only check cuddling if the - // previous one isn't a `go` call. - // We don't even have to check if it's actually cuddled or just the previous - // one because even if it's not but is a `go` statement it's valid. - if _, ok := previousNode.(*ast.GoStmt); !ok { - w.CheckCuddling(stmt, cursor, 1) - } + w.CheckExprAndExtend( + stmt, + stmt.Call, + cursor, + func(n ast.Node) (int, bool) { + _, ok := n.(*ast.GoStmt) + return 1, !ok + }, + CheckGo, + ) } func (w *WSL) CheckDefer(stmt *ast.DeferStmt, cursor *Cursor) { - defer w.CheckExpr(stmt.Call, cursor) - - if _, ok := w.Config.Checks[CheckDefer]; !ok { - return - } - - previousNode := cursor.PreviousNode() - - // We can cuddle any amount `defer` statements so only check cuddling if the - // previous one isn't a `defer` call. - if _, ok := previousNode.(*ast.DeferStmt); !ok { - w.CheckCuddling(stmt, cursor, 1) - } + w.CheckExprAndExtend( + stmt, + stmt.Call, + cursor, + func(n ast.Node) (int, bool) { + _, ok := n.(*ast.DeferStmt) + return 1, !ok + }, + CheckDefer, + ) } func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { @@ -368,6 +399,7 @@ func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { return } + // TODO: Should this be lines or statements? lastStmtInBlock := cursor.statements[len(cursor.statements)-1] if stmt == lastStmtInBlock && len(cursor.statements) <= 2 { return @@ -385,20 +417,20 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { return } - w.CheckCuddlingNoDecl(stmt, cursor, 1) + w.CheckCuddlingNoDecl(stmt, cursor, &Cursor{}, 1) } -func (w *WSL) CheckBlock(block *ast.BlockStmt) map[string]struct{} { +func (w *WSL) CheckBlock(block *ast.BlockStmt) *Cursor { w.CheckBlockLeadingNewline(block) w.CheckTrailingNewline(block) - cursor := NewCursor(block.List) - for cursor.Next() { - w.CheckStmt(cursor.Stmt(), cursor) - cursor.AddIdents(allIdents(cursor.Stmt())) - } + return w.checkBody(block.List) +} + +func (w *WSL) CheckCase(stmt *ast.CaseClause, cursor *Cursor) { + w.CheckCaseLeadingNewline(stmt) - return cursor.idents + cursor.Extend(w.checkBody(stmt.Body)) } func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { @@ -476,17 +508,18 @@ func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { } } -func (w *WSL) CheckCase(stmt *ast.CaseClause, cursor *Cursor) { - w.CheckCaseLeadingNewline(stmt) - - if _, ok := w.Config.Checks[CheckCase]; !ok { - return - } +func (w *WSL) checkBody(body []ast.Stmt) *Cursor { + cursor := NewCursor(body) + isFirst := true - cursor = NewCursor(stmt.Body) for cursor.Next() { w.CheckStmt(cursor.Stmt(), cursor) + + cursor.AddIdents(allIdents(cursor.Stmt()), isFirst) + isFirst = false } + + return cursor } func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { @@ -536,26 +569,31 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { w.CheckCase(s, cursor) // { } case *ast.BlockStmt: - cursor.idents = w.CheckBlock(s) + w.CheckBlock(s) default: fmt.Printf("Not implemented stmt: %T\n", s) } } -func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) { +func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) *Cursor { switch s := expr.(type) { // func() {} case *ast.FuncLit: - cursor.idents = w.CheckBlock(s.Body) + return w.CheckBlock(s.Body) // Call(args...) case *ast.CallExpr: + c := w.CheckExpr(s.Fun, cursor) for _, e := range s.Args { - w.CheckExpr(e, cursor) + c.Extend(w.CheckExpr(e, cursor)) } + + return c case *ast.BasicLit, *ast.CompositeLit, *ast.Ident, *ast.UnaryExpr: default: fmt.Printf("Not implemented expr: %T\n", s) } + + return NewCursor([]ast.Stmt{}) } // numberOfStatementsAbove will find out how many lines above the cursor's From 2a58931142b17072e3ee44582392ad65202e0ce7 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 13 Jan 2025 22:27:35 +0100 Subject: [PATCH 035/112] First in block improvements and config --- config.go | 6 ++-- .../with_config/first_in_block_n1/block.go | 31 +++++++++++++++++ .../first_in_block_n1/block.go.golden | 33 +++++++++++++++++++ .../with_config/first_in_block_n5/block.go | 31 +++++++++++++++++ .../first_in_block_n5/block.go.golden | 33 +++++++++++++++++++ wsl.go | 11 +++---- wsl_test.go | 14 ++++++++ 7 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 testdata/src/with_config/first_in_block_n1/block.go create mode 100644 testdata/src/with_config/first_in_block_n1/block.go.golden create mode 100644 testdata/src/with_config/first_in_block_n5/block.go create mode 100644 testdata/src/with_config/first_in_block_n5/block.go.golden diff --git a/config.go b/config.go index 5eb7415..f4e4f0c 100644 --- a/config.go +++ b/config.go @@ -55,12 +55,14 @@ Configuration migration - IncludeGenerated | Done. */ type Configuration struct { - Checks CheckSet + FirstBlockMaxDepth int + Checks CheckSet } func NewConfig() *Configuration { return &Configuration{ - Checks: DefaultChecks(), + FirstBlockMaxDepth: 1, + Checks: DefaultChecks(), } } diff --git a/testdata/src/with_config/first_in_block_n1/block.go b/testdata/src/with_config/first_in_block_n1/block.go new file mode 100644 index 0000000..b127703 --- /dev/null +++ b/testdata/src/with_config/first_in_block_n1/block.go @@ -0,0 +1,31 @@ +package testpkg + +func Fn(_ int) {} + +func fn1() { + one := 1 + two := 2 + three := 3 + + a := 1 + if true { + Fn(a) + } + + b := 1 + if true { // want "missing whitespace decreases readability" + Fn(one) + + if false { + Fn(two) + + if true { + Fn(three) + + if true { + Fn(b) + } + } + } + } +} diff --git a/testdata/src/with_config/first_in_block_n1/block.go.golden b/testdata/src/with_config/first_in_block_n1/block.go.golden new file mode 100644 index 0000000..2b3f3c0 --- /dev/null +++ b/testdata/src/with_config/first_in_block_n1/block.go.golden @@ -0,0 +1,33 @@ +package testpkg + +func Fn(_ int) {} + +func fn1() { + one := 1 + two := 2 + three := 3 + + a := 1 + if true { + Fn(a) + } + + b := 1 + + if true { // want "missing whitespace decreases readability" + Fn(one) + + if false { + Fn(two) + + if true { + Fn(three) + + if true { + Fn(b) + } + } + } + } +} + diff --git a/testdata/src/with_config/first_in_block_n5/block.go b/testdata/src/with_config/first_in_block_n5/block.go new file mode 100644 index 0000000..2a07ca9 --- /dev/null +++ b/testdata/src/with_config/first_in_block_n5/block.go @@ -0,0 +1,31 @@ +package testpkg + +func Fn(_ int) {} + +func fn1() { + one := 1 + two := 2 + three := 3 + + a := 1 + if true { + Fn(a) + } + + b := 1 + if true { + Fn(one) + + if false { + Fn(two) + + if true { + Fn(three) + + if true { + Fn(b) + } + } + } + } +} diff --git a/testdata/src/with_config/first_in_block_n5/block.go.golden b/testdata/src/with_config/first_in_block_n5/block.go.golden new file mode 100644 index 0000000..46b7e88 --- /dev/null +++ b/testdata/src/with_config/first_in_block_n5/block.go.golden @@ -0,0 +1,33 @@ +package testpkg + +func Fn(_ int) {} + +func fn1() { + one := 1 + two := 2 + three := 3 + + a := 1 + if true { + Fn(a) + } + + b := 1 + + if true { + Fn(one) + + if false { + Fn(two) + + if true { + Fn(three) + + if true { + Fn(b) + } + } + } + } +} + diff --git a/wsl.go b/wsl.go index 7e184f3..a7ea02c 100644 --- a/wsl.go +++ b/wsl.go @@ -129,14 +129,11 @@ func (w *WSL) checkCuddlingWithDecl( // FEATURE: Allow identifiers used first in block. Configurable to allow // multiple levels. - if firstInBlock { - // TODO: Make configurable - maxDepth := 1 - - for i := 0; i < maxDepth; i++ { - if i > len(blockCursor.firstIdents) { + if !wholeBlock && firstInBlock { + for i := 0; i < w.Config.FirstBlockMaxDepth; i++ { + if i < len(blockCursor.firstIdents) { firstIntersect := identIntersection( - currentIdents, + previousIdents, blockCursor.firstIdents[i], ) diff --git a/wsl_test.go b/wsl_test.go index 74cd476..0a89477 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -49,6 +49,20 @@ func TestWithConfig(t *testing.T) { config.Checks.Add(CheckWholeBlock) }, }, + { + subdir: "first_in_block_n1", + configFn: func(config *Configuration) { + config.Checks.Add(CheckFirstInBlock) + config.FirstBlockMaxDepth = 1 + }, + }, + { + subdir: "first_in_block_n5", + configFn: func(config *Configuration) { + config.Checks.Add(CheckFirstInBlock) + config.FirstBlockMaxDepth = 5 + }, + }, } { t.Run(tc.subdir, func(t *testing.T) { config := NewConfig() From 2274fc4cb9315c79cd2b112ec69664555835ebee Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 14 Jan 2025 21:55:29 +0100 Subject: [PATCH 036/112] Config and comments updates --- config.go | 20 ++++++++------------ wsl.go | 24 ++++++++++++------------ wsl_test.go | 10 +++++----- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/config.go b/config.go index f4e4f0c..ecc337a 100644 --- a/config.go +++ b/config.go @@ -32,8 +32,6 @@ const ( CheckReturn CheckSwitch CheckTypeSwitch - CheckFirstInBlock - CheckWholeBlock ) /* @@ -55,14 +53,18 @@ Configuration migration - IncludeGenerated | Done. */ type Configuration struct { - FirstBlockMaxDepth int - Checks CheckSet + AllowFirstInBlock bool + AllowWholeBlock bool + FirstInBlockMaxDepth int + Checks CheckSet } func NewConfig() *Configuration { return &Configuration{ - FirstBlockMaxDepth: 1, - Checks: DefaultChecks(), + AllowFirstInBlock: false, + AllowWholeBlock: false, + FirstInBlockMaxDepth: 1, + Checks: DefaultChecks(), } } @@ -129,8 +131,6 @@ func DefaultChecks() CheckSet { func AllChecks() CheckSet { c := DefaultChecks() c.Add(CheckErr) - c.Add(CheckFirstInBlock) - c.Add(CheckWholeBlock) return c } @@ -181,10 +181,6 @@ func CheckFromString(s string) (CheckType, error) { return CheckSwitch, nil case "type-switch": return CheckTypeSwitch, nil - case "whole-block": - return CheckWholeBlock, nil - case "first-in-block": - return CheckWholeBlock, nil default: return CheckInvalid, fmt.Errorf("invalid check '%s'", s) } diff --git a/wsl.go b/wsl.go index a7ea02c..96ff8f9 100644 --- a/wsl.go +++ b/wsl.go @@ -110,12 +110,9 @@ func (w *WSL) checkCuddlingWithDecl( return } - _, firstInBlock := w.Config.Checks[CheckFirstInBlock] - _, wholeBlock := w.Config.Checks[CheckWholeBlock] - // FEATURE: Allow identifier used anywhere in block (including recursive // blocks). - if wholeBlock { + if w.Config.AllowWholeBlock { anyIntersects := identsInMap(previousIdents, blockCursor.idents) if len(anyIntersects) > 0 { // We have matches, but too many statements above. @@ -129,8 +126,8 @@ func (w *WSL) checkCuddlingWithDecl( // FEATURE: Allow identifiers used first in block. Configurable to allow // multiple levels. - if !wholeBlock && firstInBlock { - for i := 0; i < w.Config.FirstBlockMaxDepth; i++ { + if !w.Config.AllowWholeBlock && w.Config.AllowFirstInBlock { + for i := 0; i < w.Config.FirstInBlockMaxDepth; i++ { if i < len(blockCursor.firstIdents) { firstIntersect := identIntersection( previousIdents, @@ -585,7 +582,9 @@ func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) *Cursor { } return c - case *ast.BasicLit, *ast.CompositeLit, *ast.Ident, *ast.UnaryExpr: + case *ast.BasicLit, *ast.CompositeLit, *ast.Ident, + *ast.UnaryExpr, *ast.SelectorExpr: + cursor.AddIdents(allIdents(s), false) default: fmt.Printf("Not implemented expr: %T\n", s) } @@ -782,12 +781,13 @@ func allIdents(node ast.Node) []*ast.Ident { idents = append(idents, allIdents(value)...) } case *ast.AssignStmt: - // TODO: For TypeSwitchStatements, this can be a false positive by + // NOTE: For TypeSwitchStatements, this can be a false positive by // allowing shadowing and "tricking" usage; - // var v any - - // notV := 1 - // switch notV := v.(type) {} + // + // var v any + // + // notV := 1 + // switch notV := v.(type) {} // // This would trick wsl to see `notV` used in both type switch and on // line above - faulty(?) diff --git a/wsl_test.go b/wsl_test.go index 0a89477..9b4e59a 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -46,21 +46,21 @@ func TestWithConfig(t *testing.T) { { subdir: "whole_block", configFn: func(config *Configuration) { - config.Checks.Add(CheckWholeBlock) + config.AllowWholeBlock = true }, }, { subdir: "first_in_block_n1", configFn: func(config *Configuration) { - config.Checks.Add(CheckFirstInBlock) - config.FirstBlockMaxDepth = 1 + config.AllowFirstInBlock = true + config.FirstInBlockMaxDepth = 1 }, }, { subdir: "first_in_block_n5", configFn: func(config *Configuration) { - config.Checks.Add(CheckFirstInBlock) - config.FirstBlockMaxDepth = 5 + config.AllowFirstInBlock = true + config.FirstInBlockMaxDepth = 5 }, }, } { From 30853ce14bc7527bedb4dbeeb61654eb635cf071 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 14 Jan 2025 22:21:14 +0100 Subject: [PATCH 037/112] Add case max lines --- config.go | 4 +- .../case_trailing_whitespace/case.go | 26 +++++++++++++ .../case_trailing_whitespace/case.go.golden | 28 ++++++++++++++ wsl.go | 38 +++++++++++++++++++ wsl_test.go | 6 +++ 5 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 testdata/src/with_config/case_trailing_whitespace/case.go create mode 100644 testdata/src/with_config/case_trailing_whitespace/case.go.golden diff --git a/config.go b/config.go index ecc337a..457edb4 100644 --- a/config.go +++ b/config.go @@ -41,7 +41,7 @@ Configuration migration - AllowAssignAndCallCuddle | TBD deprecate. Implemented, not configurable - AllowAssignAndAnythingCuddle | Deprecated. Replaced with `CheckAssign` - AllowMultiLineAssignCuddle | Deprecate. -- ForceCaseTrailingWhitespaceLimit | TODO +- ForceCaseTrailingWhitespaceLimit | Done. - AllowTrailingComment | TBD deprecate. Should be seen same as leading (allowed) - AllowSeparatedLeadingComment | Deprecate. Always allowed. - AllowCuddleDeclaration | Deprecate. Use `CheckDecl` instead. @@ -56,6 +56,7 @@ type Configuration struct { AllowFirstInBlock bool AllowWholeBlock bool FirstInBlockMaxDepth int + CaseMaxLines int Checks CheckSet } @@ -64,6 +65,7 @@ func NewConfig() *Configuration { AllowFirstInBlock: false, AllowWholeBlock: false, FirstInBlockMaxDepth: 1, + CaseMaxLines: 0, Checks: DefaultChecks(), } } diff --git a/testdata/src/with_config/case_trailing_whitespace/case.go b/testdata/src/with_config/case_trailing_whitespace/case.go new file mode 100644 index 0000000..b5c36f7 --- /dev/null +++ b/testdata/src/with_config/case_trailing_whitespace/case.go @@ -0,0 +1,26 @@ +package testpkg + +func fn1(n int) { + switch n { + case 1: + n++ + case 2: + n++ + n++ + case 3: + n++ + n++ + n++ // want "missing whitespace decreases readability" + case 4: + n++ + n++ + n++ + n++ + // Just a comment + default: + n++ + n++ + n++ + n++ + } +} diff --git a/testdata/src/with_config/case_trailing_whitespace/case.go.golden b/testdata/src/with_config/case_trailing_whitespace/case.go.golden new file mode 100644 index 0000000..6f3964d --- /dev/null +++ b/testdata/src/with_config/case_trailing_whitespace/case.go.golden @@ -0,0 +1,28 @@ +package testpkg + +func fn1(n int) { + switch n { + case 1: + n++ + case 2: + n++ + n++ + case 3: + n++ + n++ + n++ // want "missing whitespace decreases readability" + + case 4: + n++ + n++ + n++ + n++ + // Just a comment + default: + n++ + n++ + n++ + n++ + } +} + diff --git a/wsl.go b/wsl.go index 96ff8f9..3b7af6f 100644 --- a/wsl.go +++ b/wsl.go @@ -424,9 +424,47 @@ func (w *WSL) CheckBlock(block *ast.BlockStmt) *Cursor { func (w *WSL) CheckCase(stmt *ast.CaseClause, cursor *Cursor) { w.CheckCaseLeadingNewline(stmt) + if w.Config.CaseMaxLines != 0 { + w.checkCaseTrailingNewline(stmt, cursor) + } + cursor.Extend(w.checkBody(stmt.Body)) } +func (w *WSL) checkCaseTrailingNewline(stmt *ast.CaseClause, cursor *Cursor) { + if len(stmt.Body) == 0 { + return + } + + defer cursor.Save()() + + if !cursor.Next() { + return + } + + nextCase, ok := cursor.Stmt().(*ast.CaseClause) + if !ok { + return + } + + firstStmt := stmt.Body[0] + lastStmt := stmt.Body[len(stmt.Body)-1] + totalLines := w.lineFor(lastStmt.End()) - w.lineFor(firstStmt.Pos()) + 1 + + // Not exceeding max lines to require newline. + if totalLines <= w.Config.CaseMaxLines { + return + } + + // Next case is not immediately after the last statement so must be newline + // already. + if w.lineFor(nextCase.Pos()) > w.lineFor(lastStmt.End())+1 { + return + } + + w.addError(lastStmt.End(), nextCase.Pos(), nextCase.Pos(), MessageAddWhitespace) +} + func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { if _, ok := w.Config.Checks[CheckReturn]; !ok { return diff --git a/wsl_test.go b/wsl_test.go index 9b4e59a..507a5d5 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -63,6 +63,12 @@ func TestWithConfig(t *testing.T) { config.FirstInBlockMaxDepth = 5 }, }, + { + subdir: "case_trailing_whitespace", + configFn: func(config *Configuration) { + config.CaseMaxLines = 2 + }, + }, } { t.Run(tc.subdir, func(t *testing.T) { config := NewConfig() From ad678d8273a79e6aa3083ed69b8c3daa6f575b7f Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 14 Jan 2025 22:28:01 +0100 Subject: [PATCH 038/112] Add more types to detect idents --- wsl.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/wsl.go b/wsl.go index 3b7af6f..0140773 100644 --- a/wsl.go +++ b/wsl.go @@ -515,6 +515,7 @@ func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { previousNode := cursor.PreviousNode() var appendNode *ast.CallExpr + for _, expr := range stmt.Rhs { e, ok := expr.(*ast.CallExpr) if !ok { @@ -882,6 +883,28 @@ func allIdents(node ast.Node) []*ast.Ident { for _, r := range n.Results { idents = append(idents, allIdents(r)...) } + case *ast.KeyValueExpr: + idents = append(idents, allIdents(n.Key)...) + idents = append(idents, allIdents(n.Value)...) + case *ast.MapType: + idents = append(idents, allIdents(n.Key)...) + idents = append(idents, allIdents(n.Value)...) + case *ast.StarExpr: + idents = append(idents, allIdents(n.X)...) + case *ast.IndexExpr: + idents = append(idents, allIdents(n.X)...) + idents = append(idents, allIdents(n.Index)...) + case *ast.SliceExpr: + idents = append(idents, allIdents(n.X)...) + idents = append(idents, allIdents(n.Low)...) + idents = append(idents, allIdents(n.High)...) + idents = append(idents, allIdents(n.Max)...) + case *ast.FieldList: + for _, f := range n.List { + idents = append(idents, allIdents(f)...) + } + case *ast.StructType: + idents = append(idents, allIdents(n.Fields)...) case *ast.BasicLit, *ast.FuncLit, *ast.BranchStmt, *ast.ArrayType: default: From a325719a43e065f6b0e476c77ce4ae9dd63e213d Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 16 Jan 2025 20:35:20 +0100 Subject: [PATCH 039/112] Fix false positives found on wsl --- config.go | 4 +++- cursor.go | 1 + wsl.go | 14 +++++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/config.go b/config.go index 457edb4..73ff4bc 100644 --- a/config.go +++ b/config.go @@ -57,15 +57,17 @@ type Configuration struct { AllowWholeBlock bool FirstInBlockMaxDepth int CaseMaxLines int + ReturnMaxLines int Checks CheckSet } func NewConfig() *Configuration { return &Configuration{ - AllowFirstInBlock: false, + AllowFirstInBlock: true, AllowWholeBlock: false, FirstInBlockMaxDepth: 1, CaseMaxLines: 0, + ReturnMaxLines: 2, Checks: DefaultChecks(), } } diff --git a/cursor.go b/cursor.go index 189a94d..e093680 100644 --- a/cursor.go +++ b/cursor.go @@ -154,6 +154,7 @@ func (c *Cursor) Stmt() ast.Stmt { func (c *Cursor) Save() func() { idx := c.currentIdx + return func() { c.currentIdx = idx } diff --git a/wsl.go b/wsl.go index 0140773..9d6f0c0 100644 --- a/wsl.go +++ b/wsl.go @@ -466,6 +466,10 @@ func (w *WSL) checkCaseTrailingNewline(stmt *ast.CaseClause, cursor *Cursor) { } func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { + for _, expr := range stmt.Results { + w.CheckExpr(expr, cursor) + } + if _, ok := w.Config.Checks[CheckReturn]; !ok { return } @@ -476,10 +480,14 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { return } + if w.numberOfStatementsAbove(cursor) == 0 { + return + } + // If the distance between the first statement and the return statement is // less than 3 LOC we're allowed to cuddle. firstStmts := cursor.Nth(0) - if w.lineFor(stmt.End())-w.lineFor(firstStmts.Pos()) < 2 { + if w.lineFor(stmt.End())-w.lineFor(firstStmts.Pos()) < w.Config.ReturnMaxLines { return } @@ -512,6 +520,10 @@ func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { return } + if w.numberOfStatementsAbove(cursor) == 0 { + return + } + previousNode := cursor.PreviousNode() var appendNode *ast.CallExpr From c56cf2ad011d8bd7816e12e79997a1128f5defdd Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 16 Jan 2025 20:54:24 +0100 Subject: [PATCH 040/112] Add support for exclusive declarations --- config.go | 6 +++++- .../with_config/exclusive_short_decl/code.go | 15 +++++++++++++++ .../exclusive_short_decl/code.go.golden | 17 +++++++++++++++++ wsl.go | 17 +++++++++++++++-- wsl_test.go | 6 ++++++ 5 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 testdata/src/with_config/exclusive_short_decl/code.go create mode 100644 testdata/src/with_config/exclusive_short_decl/code.go.golden diff --git a/config.go b/config.go index 73ff4bc..1c56c6a 100644 --- a/config.go +++ b/config.go @@ -16,6 +16,7 @@ type CheckType int const ( CheckInvalid CheckType = iota CheckAssign + CheckAssignExclusive CheckAppend CheckBreak CheckContinue @@ -49,7 +50,7 @@ Configuration migration - AllowCuddleWithRHS | TBD deprecate. Should not be needed. Not clear why separate from above - ForceCuddleErrCheckAndAssign | Deprecate. Replaced with `CheckErr` - ErrorVariableNames | Deprecate. We're now looking if the variable implements the error interface -- ForceExclusiveShortDeclarations | TODO +- ForceExclusiveShortDeclarations | Done. - IncludeGenerated | Done. */ type Configuration struct { @@ -134,6 +135,7 @@ func DefaultChecks() CheckSet { func AllChecks() CheckSet { c := DefaultChecks() + c.Add(CheckAssignExclusive) c.Add(CheckErr) return c @@ -155,6 +157,8 @@ func CheckFromString(s string) (CheckType, error) { switch strings.ToLower(s) { case "assign": return CheckAssign, nil + case "assign-exclusive": + return CheckAssignExclusive, nil case "append": return CheckAppend, nil case "break": diff --git a/testdata/src/with_config/exclusive_short_decl/code.go b/testdata/src/with_config/exclusive_short_decl/code.go new file mode 100644 index 0000000..f5e1039 --- /dev/null +++ b/testdata/src/with_config/exclusive_short_decl/code.go @@ -0,0 +1,15 @@ +package testpkg + +func fn() { + a := 1 + b := 2 + c := 3 + + a = 1 + b = 1 + c = 1 + + d := 1 + a = 3 // want "missing whitespace decreases readability" + e := 4 // want "missing whitespace decreases readability" +} diff --git a/testdata/src/with_config/exclusive_short_decl/code.go.golden b/testdata/src/with_config/exclusive_short_decl/code.go.golden new file mode 100644 index 0000000..3dcced7 --- /dev/null +++ b/testdata/src/with_config/exclusive_short_decl/code.go.golden @@ -0,0 +1,17 @@ +package testpkg + +func fn() { + a := 1 + b := 2 + c := 3 + + a = 1 + b = 1 + c = 1 + + d := 1 + + a = 3 // want "missing whitespace decreases readability" + + e := 4 // want "missing whitespace decreases readability" +} diff --git a/wsl.go b/wsl.go index 9d6f0c0..ea09351 100644 --- a/wsl.go +++ b/wsl.go @@ -169,10 +169,10 @@ func (w *WSL) checkCuddlingWithDecl( func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { previousNode := cursor.PreviousNode() - _, prevIsAssign := previousNode.(*ast.AssignStmt) + previousAssign, prevIsAssign := previousNode.(*ast.AssignStmt) _, prevIsDecl := previousNode.(*ast.DeclStmt) _, prevIsIncDec := previousNode.(*ast.IncDecStmt) - _, currIsAssign := stmt.(*ast.AssignStmt) + currAssign, currIsAssign := stmt.(*ast.AssignStmt) // Most of the time we allow cuddling with declarations (var) if it's just // one statement but not always so this can be disabled, e.g. for @@ -181,6 +181,19 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { prevIsDecl = false } + // If we enable exclusive assign checks we only allow new declarations or + // new assignments together but not mix and match. + // When this is enabled we also implicitly disable support to cuddle with + // anything else. + if _, ok := w.Config.Checks[CheckAssignExclusive]; ok { + prevIsDecl = false + prevIsIncDec = false + + if prevIsAssign && currIsAssign { + prevIsAssign = previousAssign.Tok == currAssign.Tok + } + } + prevIsValidType := previousNode == nil || prevIsAssign || prevIsDecl || prevIsIncDec // TODO: This is `allow-assign-and-call`/`AllowAssignAndCallCuddle`, should diff --git a/wsl_test.go b/wsl_test.go index 507a5d5..1cb999d 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -69,6 +69,12 @@ func TestWithConfig(t *testing.T) { config.CaseMaxLines = 2 }, }, + { + subdir: "exclusive_short_decl", + configFn: func(config *Configuration) { + config.Checks.Add(CheckAssignExclusive) + }, + }, } { t.Run(tc.subdir, func(t *testing.T) { config := NewConfig() From 41109713261d425889b8aa5f7d8fb4c93a71e17c Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 16 Jan 2025 20:59:43 +0100 Subject: [PATCH 041/112] Fix broken tests --- testdata/src/default_config/for/for.go | 10 ++++++++-- testdata/src/default_config/for/for.go.golden | 10 ++++++++-- testdata/src/default_config/if/if.go | 10 ++++++++-- testdata/src/default_config/if/if.go.golden | 10 ++++++++-- testdata/src/default_config/range/range.go | 10 ++++++++-- testdata/src/default_config/range/range.go.golden | 10 ++++++++-- 6 files changed, 48 insertions(+), 12 deletions(-) diff --git a/testdata/src/default_config/for/for.go b/testdata/src/default_config/for/for.go index af824ca..90ba133 100644 --- a/testdata/src/default_config/for/for.go +++ b/testdata/src/default_config/for/for.go @@ -4,16 +4,22 @@ func fn1() { a := 1 b := 2 // want "missing whitespace decreases readability" for i := 0; i < b; i++ { - panic(a) + panic(1) } + + _ = a + _ = b } func fn2() { b := 2 a := 1 for i := 0; i < b; i++ { // want "missing whitespace decreases readability" - panic(a) + panic(1) } + + _ = a + _ = b } func fn3() { diff --git a/testdata/src/default_config/for/for.go.golden b/testdata/src/default_config/for/for.go.golden index b10799e..48a5b0b 100644 --- a/testdata/src/default_config/for/for.go.golden +++ b/testdata/src/default_config/for/for.go.golden @@ -5,8 +5,11 @@ func fn1() { b := 2 // want "missing whitespace decreases readability" for i := 0; i < b; i++ { - panic(a) + panic(1) } + + _ = a + _ = b } func fn2() { @@ -14,8 +17,11 @@ func fn2() { a := 1 for i := 0; i < b; i++ { // want "missing whitespace decreases readability" - panic(a) + panic(1) } + + _ = a + _ = b } func fn3() { diff --git a/testdata/src/default_config/if/if.go b/testdata/src/default_config/if/if.go index 48e5adc..ce158d8 100644 --- a/testdata/src/default_config/if/if.go +++ b/testdata/src/default_config/if/if.go @@ -6,16 +6,22 @@ func fn1() { a := 1 b := 2 // want "missing whitespace decreases readability" if b == 2 { - panic(a) + panic(1) } + + _ = a + _ = b } func fn2() { b := 2 a := 1 if b == 2 { // want "missing whitespace decreases readability" - panic(a) + panic(1) } + + _ = a + _ = b } func fn3() { diff --git a/testdata/src/default_config/if/if.go.golden b/testdata/src/default_config/if/if.go.golden index d6c15e6..738b330 100644 --- a/testdata/src/default_config/if/if.go.golden +++ b/testdata/src/default_config/if/if.go.golden @@ -7,8 +7,11 @@ func fn1() { b := 2 // want "missing whitespace decreases readability" if b == 2 { - panic(a) + panic(1) } + + _ = a + _ = b } func fn2() { @@ -16,8 +19,11 @@ func fn2() { a := 1 if b == 2 { // want "missing whitespace decreases readability" - panic(a) + panic(1) } + + _ = a + _ = b } func fn3() { diff --git a/testdata/src/default_config/range/range.go b/testdata/src/default_config/range/range.go index 1a8a0b1..3c3c5d1 100644 --- a/testdata/src/default_config/range/range.go +++ b/testdata/src/default_config/range/range.go @@ -4,16 +4,22 @@ func fn1() { a := []int{} b := []int{} // want "missing whitespace decreases readability" for range b { - panic(a) + panic(1) } + + _ = a + _ = b } func fn2() { b := []int{} a := []int{} for range b { // want "missing whitespace decreases readability" - panic(a) + panic(1) } + + _ = a + _ = b } func fn3() { diff --git a/testdata/src/default_config/range/range.go.golden b/testdata/src/default_config/range/range.go.golden index 427a22d..be028a0 100644 --- a/testdata/src/default_config/range/range.go.golden +++ b/testdata/src/default_config/range/range.go.golden @@ -5,8 +5,11 @@ func fn1() { b := []int{} // want "missing whitespace decreases readability" for range b { - panic(a) + panic(1) } + + _ = a + _ = b } func fn2() { @@ -14,8 +17,11 @@ func fn2() { a := []int{} for range b { // want "missing whitespace decreases readability" - panic(a) + panic(1) } + + _ = a + _ = b } func fn3() { From 33486f2df95ede7b19df5d80997787bb87120db5 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Thu, 16 Jan 2025 21:05:41 +0100 Subject: [PATCH 042/112] Linting and versioning --- .github/workflows/go.yml | 43 ++++++++++++++++++++-------------------- .golangci.yml | 35 ++++++++++++-------------------- config.go | 3 ++- cursor.go | 6 ++---- go.mod | 11 +++++----- go.sum | 12 +++-------- wsl.go | 12 +++++------ 7 files changed, 53 insertions(+), 69 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d8d91e4..bde69c2 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -9,11 +9,13 @@ jobs: name: golangci-lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: - go-version: '1.20' - - uses: actions/checkout@v3 - - uses: golangci/golangci-lint-action@v3 + go-version: stable + - uses: golangci/golangci-lint-action@v6 + with: + version: v1.62.2 tests: # run after golangci-lint action to not produce duplicated errors @@ -22,40 +24,37 @@ jobs: strategy: matrix: go: - - '1.20' + - oldstable + - stable os: - ubuntu-latest runs-on: ${{ matrix.os }} steps: - - name: Install Go - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.go }} - - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - name: Setup cache - uses: actions/cache@v3 + - name: Install Go + uses: actions/setup-go@v5 with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + go-version: ${{ matrix.go }} - name: Test code run: go test -race -v ./... + - name: Run + run: go run ./cmd/wsl/ ./... + coverage: runs-on: ubuntu-latest steps: - - name: Install Go + - name: Checkout code if: success() - uses: actions/setup-go@v3 - with: - go-version: '1.20' + uses: actions/checkout@v4 - - name: Checkout code - uses: actions/checkout@v3 + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: stable - name: Calc coverage run: | diff --git a/.golangci.yml b/.golangci.yml index 5430120..4966964 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,24 +1,25 @@ --- run: - deadline: 1m + timeout: 1m issues-exit-code: 1 tests: true - skip-dirs: - - vendor$ output: - format: colored-line-number print-issued-lines: false + sort-results: true + formats: + - format: colored-line-number linters-settings: gocognit: min-complexity: 10 depguard: - list-type: blacklist - include-go-root: false - packages: - - github.com/davecgh/go-spew/spew + rules: + main: + deny: + - pkg: "github.com/davecgh/go-spew/spew" + desc: not allowed misspell: locale: US @@ -38,44 +39,34 @@ linters: enable-all: true disable: - cyclop - - deadcode - depguard - dupl - dupword - - exhaustivestruct + - err113 - exhaustruct + - exportloopref - forbidigo - funlen - gci - gocognit - gocyclo - godox - - golint - - gomnd - - ifshort - - interfacer + - mnd - lll - maintidx - - maligned - nakedret - nestif - nlreturn - - nosnakecase - paralleltest - prealloc - rowserrcheck - - scopelint - - structcheck - testpackage - - varcheck + - tparallel - varnamelen - wastedassign - fast: false - issues: exclude-use-default: true max-issues-per-linter: 0 max-same-issues: 0 - # vim: set sw=2 ts=2 et: diff --git a/config.go b/config.go index 1c56c6a..b65dbf8 100644 --- a/config.go +++ b/config.go @@ -1,6 +1,7 @@ package wsl import ( + "errors" "fmt" "strings" ) @@ -80,7 +81,7 @@ func (c *Configuration) Update( disable []string, ) error { if enableAll && disableAll { - return fmt.Errorf("can't use both `enable-all` and `disable-all`") + return errors.New("can't use both `enable-all` and `disable-all`") } if enableAll { diff --git a/cursor.go b/cursor.go index e093680..f29e156 100644 --- a/cursor.go +++ b/cursor.go @@ -28,7 +28,7 @@ var ErrCursourOutObFounds = errors.New("out of bounds") // } // // idents will be a map containing [a, b, c, d] and first identifiers will be -// [[a, b], [c]] +// [[a, b], [c]]. type Cursor struct { currentIdx int statements []ast.Stmt @@ -65,9 +65,7 @@ func (c *Cursor) Merge(otherCursor *Cursor) { if len(c.firstIdents) == 0 { c.firstIdents = append(c.firstIdents, otherFirstIdents) } else { - for _, v := range otherFirstIdents { - c.firstIdents[0] = append(c.firstIdents[0], v) - } + c.firstIdents[0] = append(c.firstIdents[0], otherFirstIdents...) } } diff --git a/go.mod b/go.mod index 6702604..a72643e 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,15 @@ module github.com/bombsimon/wsl/v4 -go 1.19 +go 1.23 -require golang.org/x/tools v0.19.0 +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/stretchr/testify v1.9.0 + golang.org/x/tools v0.19.0 +) require ( - github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.9.0 // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/sys v0.18.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b451e5e..cbec71b 100644 --- a/go.sum +++ b/go.sum @@ -4,19 +4,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/wsl.go b/wsl.go index ea09351..8c31bfe 100644 --- a/wsl.go +++ b/wsl.go @@ -53,11 +53,11 @@ func (w *WSL) Run() { } } -func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, blockCursor *Cursor, maxAllowedStatements int) { +func (w *WSL) CheckCuddling(stmt ast.Node, cursor, blockCursor *Cursor, maxAllowedStatements int) { w.checkCuddlingWithDecl(stmt, cursor, blockCursor, maxAllowedStatements, true) } -func (w *WSL) CheckCuddlingNoDecl(stmt ast.Node, cursor *Cursor, blockCursor *Cursor, maxAllowedStatements int) { +func (w *WSL) CheckCuddlingNoDecl(stmt ast.Node, cursor, blockCursor *Cursor, maxAllowedStatements int) { w.checkCuddlingWithDecl(stmt, cursor, blockCursor, maxAllowedStatements, false) } @@ -127,7 +127,7 @@ func (w *WSL) checkCuddlingWithDecl( // FEATURE: Allow identifiers used first in block. Configurable to allow // multiple levels. if !w.Config.AllowWholeBlock && w.Config.AllowFirstInBlock { - for i := 0; i < w.Config.FirstInBlockMaxDepth; i++ { + for i := range w.Config.FirstInBlockMaxDepth { if i < len(blockCursor.firstIdents) { firstIntersect := identIntersection( previousIdents, @@ -682,9 +682,9 @@ func (w *WSL) CheckBlockLeadingNewline(body *ast.BlockStmt) { w.CheckLeadingNewline(body.Lbrace, body.List, comments) } -func (w *WSL) CheckCaseLeadingNewline(case_ *ast.CaseClause) { - comments := ast.NewCommentMap(w.Fset, case_, w.File.Comments) - w.CheckLeadingNewline(case_.Colon, case_.Body, comments) +func (w *WSL) CheckCaseLeadingNewline(caseClause *ast.CaseClause) { + comments := ast.NewCommentMap(w.Fset, caseClause, w.File.Comments) + w.CheckLeadingNewline(caseClause.Colon, caseClause.Body, comments) } func (w *WSL) CheckLeadingNewline(startPos token.Pos, body []ast.Stmt, comments ast.CommentMap) { From 62ab7f386f3f98e40389153cb0f8489fa67dcfa8 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 19 Jan 2025 18:34:29 +0100 Subject: [PATCH 043/112] Some more types --- wsl.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 11 deletions(-) diff --git a/wsl.go b/wsl.go index 8c31bfe..8e1e6ad 100644 --- a/wsl.go +++ b/wsl.go @@ -438,14 +438,24 @@ func (w *WSL) CheckCase(stmt *ast.CaseClause, cursor *Cursor) { w.CheckCaseLeadingNewline(stmt) if w.Config.CaseMaxLines != 0 { - w.checkCaseTrailingNewline(stmt, cursor) + w.checkCaseTrailingNewline(stmt.Body, cursor) } cursor.Extend(w.checkBody(stmt.Body)) } -func (w *WSL) checkCaseTrailingNewline(stmt *ast.CaseClause, cursor *Cursor) { - if len(stmt.Body) == 0 { +func (w *WSL) CheckComm(stmt *ast.CommClause, cursor *Cursor) { + w.CheckCommLeadingNewline(stmt) + + if w.Config.CaseMaxLines != 0 { + w.checkCaseTrailingNewline(stmt.Body, cursor) + } + + cursor.Extend(w.checkBody(stmt.Body)) +} + +func (w *WSL) checkCaseTrailingNewline(body []ast.Stmt, cursor *Cursor) { + if len(body) == 0 { return } @@ -455,13 +465,18 @@ func (w *WSL) checkCaseTrailingNewline(stmt *ast.CaseClause, cursor *Cursor) { return } - nextCase, ok := cursor.Stmt().(*ast.CaseClause) - if !ok { + var nextCase ast.Node + switch n := cursor.Stmt().(type) { + case *ast.CaseClause: + nextCase = n + case *ast.CommClause: + nextCase = n + default: return } - firstStmt := stmt.Body[0] - lastStmt := stmt.Body[len(stmt.Body)-1] + firstStmt := body[0] + lastStmt := body[len(body)-1] totalLines := w.lineFor(lastStmt.End()) - w.lineFor(firstStmt.Pos()) + 1 // Not exceeding max lines to require newline. @@ -625,9 +640,19 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { // case: case *ast.CaseClause: w.CheckCase(s, cursor) + // case: + case *ast.CommClause: + w.CheckComm(s, cursor) // { } case *ast.BlockStmt: w.CheckBlock(s) + // select { } + case *ast.SelectStmt: + w.CheckBlock(s.Body) + // ch <- ... + case *ast.SendStmt: + // TODO: Check cuddling? + w.CheckExpr(s.Value, cursor) default: fmt.Printf("Not implemented stmt: %T\n", s) } @@ -646,8 +671,22 @@ func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) *Cursor { } return c - case *ast.BasicLit, *ast.CompositeLit, *ast.Ident, - *ast.UnaryExpr, *ast.SelectorExpr: + case *ast.IndexExpr: + return w.CheckExpr(s.X, cursor) + case *ast.StarExpr: + return w.CheckExpr(s.X, cursor) + case *ast.ArrayType, + *ast.BasicLit, + *ast.BinaryExpr, + *ast.ChanType, + *ast.CompositeLit, + *ast.Ident, + *ast.MapType, + *ast.ParenExpr, + *ast.SelectorExpr, + *ast.SliceExpr, + *ast.TypeAssertExpr, + *ast.UnaryExpr: cursor.AddIdents(allIdents(s), false) default: fmt.Printf("Not implemented expr: %T\n", s) @@ -687,6 +726,11 @@ func (w *WSL) CheckCaseLeadingNewline(caseClause *ast.CaseClause) { w.CheckLeadingNewline(caseClause.Colon, caseClause.Body, comments) } +func (w *WSL) CheckCommLeadingNewline(commClause *ast.CommClause) { + comments := ast.NewCommentMap(w.Fset, commClause, w.File.Comments) + w.CheckLeadingNewline(commClause.Colon, commClause.Body, comments) +} + func (w *WSL) CheckLeadingNewline(startPos token.Pos, body []ast.Stmt, comments ast.CommentMap) { if _, ok := w.Config.Checks[CheckLeadingWhitespace]; !ok { return @@ -930,8 +974,24 @@ func allIdents(node ast.Node) []*ast.Ident { } case *ast.StructType: idents = append(idents, allIdents(n.Fields)...) - case *ast.BasicLit, *ast.FuncLit, *ast.BranchStmt, - *ast.ArrayType: + case *ast.ParenExpr: + idents = append(idents, allIdents(n.X)...) + case *ast.SendStmt: + idents = append(idents, allIdents(n.Chan)...) + idents = append(idents, allIdents(n.Value)...) + case *ast.ChanType: + idents = append(idents, allIdents(n.Value)...) + case *ast.CommClause: + for _, s := range n.Body { + idents = append(idents, allIdents(s)...) + } + case *ast.ArrayType, + *ast.BasicLit, + *ast.BlockStmt, + *ast.BranchStmt, + *ast.FuncLit, + *ast.SelectStmt, + *ast.TypeSpec: default: spew.Dump(node) fmt.Printf("missing ident detection for %T\n", node) From 3a00ab0dd36c060ac357ae9166e5ca185f7d0ad4 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 19 Jan 2025 20:10:11 +0100 Subject: [PATCH 044/112] Fix if false positives --- testdata/src/default_config/if/if.go | 9 +++++++++ testdata/src/default_config/if/if.go.golden | 9 +++++++++ wsl.go | 4 +++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/testdata/src/default_config/if/if.go b/testdata/src/default_config/if/if.go index ce158d8..556b6a1 100644 --- a/testdata/src/default_config/if/if.go +++ b/testdata/src/default_config/if/if.go @@ -40,3 +40,12 @@ func fn4() { panic(a) } } + +func fn5(m any, k string) string { + v := m.(map[string]string) + if r, ok := m[k]; ok { + return r + } + + return k +} diff --git a/testdata/src/default_config/if/if.go.golden b/testdata/src/default_config/if/if.go.golden index 738b330..9c0fee1 100644 --- a/testdata/src/default_config/if/if.go.golden +++ b/testdata/src/default_config/if/if.go.golden @@ -43,3 +43,12 @@ func fn4() { panic(a) } } + +func fn5(m any, k string) string { + v := m.(map[string]string) + if r, ok := m[k]; ok { + return r + } + + return k +} diff --git a/wsl.go b/wsl.go index 8e1e6ad..1a7ee95 100644 --- a/wsl.go +++ b/wsl.go @@ -910,8 +910,10 @@ func allIdents(node ast.Node) []*ast.Ident { idents = append(idents, allIdents(rhs)...) } case *ast.IfStmt: + idents = append(idents, allIdents(n.Init)...) idents = append(idents, allIdents(n.Cond)...) - // TODO: idents = append(idents, allIdents(n.Else)...) + // TODO + // idents = append(idents, allIdents(n.Else)...) case *ast.BinaryExpr: idents = append(idents, allIdents(n.X)...) idents = append(idents, allIdents(n.Y)...) From b9452132ee232c04b6472f7e1d42819ac5c495a9 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 20 Jan 2025 21:46:51 +0100 Subject: [PATCH 045/112] Label statement --- config.go | 2 + testdata/src/default_config/label/label.go | 37 +++++++++++++++++ .../src/default_config/label/label.go.golden | 40 +++++++++++++++++++ wsl.go | 23 +++++++++++ 4 files changed, 102 insertions(+) create mode 100644 testdata/src/default_config/label/label.go create mode 100644 testdata/src/default_config/label/label.go.golden diff --git a/config.go b/config.go index b65dbf8..8f6fadb 100644 --- a/config.go +++ b/config.go @@ -28,6 +28,7 @@ const ( CheckFor CheckGo CheckIf + CheckLabel CheckLeadingWhitespace CheckTrailingWhitespace CheckRange @@ -125,6 +126,7 @@ func DefaultChecks() CheckSet { CheckFor: {}, CheckGo: {}, CheckIf: {}, + CheckLabel: {}, CheckLeadingWhitespace: {}, CheckTrailingWhitespace: {}, CheckRange: {}, diff --git a/testdata/src/default_config/label/label.go b/testdata/src/default_config/label/label.go new file mode 100644 index 0000000..896450c --- /dev/null +++ b/testdata/src/default_config/label/label.go @@ -0,0 +1,37 @@ +package testpkg + +func fn1() { + for i := range make([]int, 2) { + if i == 1 { + goto END + } + + END: + _ = 1 + } +} + +func fn2() { + for i := range make([]int, 2) { + if i == 1 { + goto END + } + + END: + } +} + +func fn3() { +L1: + if true { + _ = 1 + } +L2: // want "missing whitespace decreases readability" + if true { + _ = 1 + } + + _ = 1 +L3: // want "missing whitespace decreases readability" + _ = 1 +} diff --git a/testdata/src/default_config/label/label.go.golden b/testdata/src/default_config/label/label.go.golden new file mode 100644 index 0000000..0efac98 --- /dev/null +++ b/testdata/src/default_config/label/label.go.golden @@ -0,0 +1,40 @@ +package testpkg + +func fn1() { + for i := range make([]int, 2) { + if i == 1 { + goto END + } + + END: + _ = 1 + } +} + +func fn2() { + for i := range make([]int, 2) { + if i == 1 { + goto END + } + + END: + } +} + +func fn3() { +L1: + if true { + _ = 1 + } + +L2: // want "missing whitespace decreases readability" + if true { + _ = 1 + } + + + _ = 1 + +L3: // want "missing whitespace decreases readability" + _ = 1 +} diff --git a/wsl.go b/wsl.go index 1a7ee95..02eeedc 100644 --- a/wsl.go +++ b/wsl.go @@ -543,6 +543,18 @@ func (w *WSL) CheckIncDec(stmt *ast.IncDecStmt, cursor *Cursor) { w.CheckCuddlingWithoutIntersection(stmt, cursor) } +func (w *WSL) CheckLabel(stmt *ast.LabeledStmt, cursor *Cursor) { + if _, ok := w.Config.Checks[CheckLabel]; !ok { + return + } + + if w.numberOfStatementsAbove(cursor) == 0 { + return + } + + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) +} + func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { if _, ok := w.Config.Checks[CheckAppend]; !ok { return @@ -653,6 +665,9 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { case *ast.SendStmt: // TODO: Check cuddling? w.CheckExpr(s.Value, cursor) + // LABEL: + case *ast.LabeledStmt: + w.CheckLabel(s, cursor) default: fmt.Printf("Not implemented stmt: %T\n", s) } @@ -809,6 +824,13 @@ func (w *WSL) CheckTrailingNewline(body *ast.BlockStmt) { closingPos := body.Rbrace lastStmtOrComment := lastStmt.End() + // Empty label statements needs positional adjustment. #92 + if l, ok := lastStmt.(*ast.LabeledStmt); ok { + if _, ok := l.Stmt.(*ast.EmptyStmt); ok { + lastStmtOrComment = lastStmt.Pos() + } + } + comments := ast.NewCommentMap(w.Fset, body, w.File.Comments) for _, commentGroup := range comments { for _, comment := range commentGroup { @@ -992,6 +1014,7 @@ func allIdents(node ast.Node) []*ast.Ident { *ast.BlockStmt, *ast.BranchStmt, *ast.FuncLit, + *ast.LabeledStmt, *ast.SelectStmt, *ast.TypeSpec: default: From d837d8ee58cf27e93c12a4deee739e909eab59a5 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 20 Jan 2025 21:58:26 +0100 Subject: [PATCH 046/112] Improve/add select --- config.go | 2 ++ testdata/src/default_config/select/select.go | 28 ++++++++++++++++++ .../default_config/select/select.go.golden | 29 +++++++++++++++++++ .../case_trailing_whitespace/case.go | 16 ++++++++++ .../case_trailing_whitespace/case.go.golden | 16 ++++++++++ wsl.go | 6 +++- 6 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 testdata/src/default_config/select/select.go create mode 100644 testdata/src/default_config/select/select.go.golden diff --git a/config.go b/config.go index 8f6fadb..73db832 100644 --- a/config.go +++ b/config.go @@ -33,6 +33,7 @@ const ( CheckTrailingWhitespace CheckRange CheckReturn + CheckSelect CheckSwitch CheckTypeSwitch ) @@ -131,6 +132,7 @@ func DefaultChecks() CheckSet { CheckTrailingWhitespace: {}, CheckRange: {}, CheckReturn: {}, + CheckSelect: {}, CheckSwitch: {}, CheckTypeSwitch: {}, } diff --git a/testdata/src/default_config/select/select.go b/testdata/src/default_config/select/select.go new file mode 100644 index 0000000..26d2680 --- /dev/null +++ b/testdata/src/default_config/select/select.go @@ -0,0 +1,28 @@ +package testpkg + +import "context" + +func fn1(ctx context.Context, ch1 chan struct{}) { + select { + case ctx.Done(): + _ = 1 + case <-ch1: + _ = 1 + default: + _ = 1 + } +} + +func fn1(ctx context.Context, ch1 chan struct{}) { + x := 1 + select { // want "missing whitespace decreases readability" + case ctx.Done(): + _ = 1 + case <-ch1: + _ = 1 + default: + _ = 1 + } + + _ = x +} diff --git a/testdata/src/default_config/select/select.go.golden b/testdata/src/default_config/select/select.go.golden new file mode 100644 index 0000000..a9c3786 --- /dev/null +++ b/testdata/src/default_config/select/select.go.golden @@ -0,0 +1,29 @@ +package testpkg + +import "context" + +func fn1(ctx context.Context, ch1 chan struct{}) { + select { + case ctx.Done(): + _ = 1 + case <-ch1: + _ = 1 + default: + _ = 1 + } +} + +func fn1(ctx context.Context, ch1 chan struct{}) { + x := 1 + + select { // want "missing whitespace decreases readability" + case ctx.Done(): + _ = 1 + case <-ch1: + _ = 1 + default: + _ = 1 + } + + _ = x +} diff --git a/testdata/src/with_config/case_trailing_whitespace/case.go b/testdata/src/with_config/case_trailing_whitespace/case.go index b5c36f7..545734f 100644 --- a/testdata/src/with_config/case_trailing_whitespace/case.go +++ b/testdata/src/with_config/case_trailing_whitespace/case.go @@ -1,5 +1,7 @@ package testpkg +import "context" + func fn1(n int) { switch n { case 1: @@ -24,3 +26,17 @@ func fn1(n int) { n++ } } + +func fn2(ctx context.Context, ch1 chan struct{}) { + select { + case ctx.Done(): + _ = 1 + _ = 1 + _ = 1 + _ = 1 // want "missing whitespace decreases readability" + case <-ch1: + _ = 1 + default: + _ = 1 + } +} diff --git a/testdata/src/with_config/case_trailing_whitespace/case.go.golden b/testdata/src/with_config/case_trailing_whitespace/case.go.golden index 6f3964d..08e2e4c 100644 --- a/testdata/src/with_config/case_trailing_whitespace/case.go.golden +++ b/testdata/src/with_config/case_trailing_whitespace/case.go.golden @@ -1,5 +1,7 @@ package testpkg +import "context" + func fn1(n int) { switch n { case 1: @@ -26,3 +28,17 @@ func fn1(n int) { } } +func fn2(ctx context.Context, ch1 chan struct{}) { + select { + case ctx.Done(): + _ = 1 + _ = 1 + _ = 1 + _ = 1 // want "missing whitespace decreases readability" + + case <-ch1: + _ = 1 + default: + _ = 1 + } +} diff --git a/wsl.go b/wsl.go index 02eeedc..2818672 100644 --- a/wsl.go +++ b/wsl.go @@ -354,6 +354,10 @@ func (w *WSL) CheckTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { w.CheckBlockAndExtend(stmt, stmt.Body, cursor, CheckTypeSwitch) } +func (w *WSL) CheckSelect(stmt *ast.SelectStmt, cursor *Cursor) { + w.CheckBlockAndExtend(stmt, stmt.Body, cursor, CheckSelect) +} + func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { w.CheckExprAndExtend( stmt, @@ -660,7 +664,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { w.CheckBlock(s) // select { } case *ast.SelectStmt: - w.CheckBlock(s.Body) + w.CheckSelect(s, cursor) // ch <- ... case *ast.SendStmt: // TODO: Check cuddling? From e2ee9948b1095b2259bc3cc2835d14b071a56279 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 21 Jan 2025 22:00:25 +0100 Subject: [PATCH 047/112] More assign test --- testdata/src/default_config/assign/assign.go | 17 +++++++++++++++++ .../src/default_config/assign/assign.go.golden | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/testdata/src/default_config/assign/assign.go b/testdata/src/default_config/assign/assign.go index c409cab..1175b54 100644 --- a/testdata/src/default_config/assign/assign.go +++ b/testdata/src/default_config/assign/assign.go @@ -1,5 +1,10 @@ package testpkg +import ( + "bytes" + "encoding/json" +) + type T struct { I int } @@ -12,6 +17,11 @@ func (*T) Fn() int { return 1 } +func FnC(_ string, fn func() error) error { + fn() + return nil +} + func strictAppend() { s := []string{} s = append(s, "a") @@ -39,3 +49,10 @@ func assignAndCall() { t1.Fn() t2.I = t1.Fn() } + +func closureInCall() { + buf := &bytes.Buffer{} + err := Fnc("buf", func() error { + return json.NewEncoder(buf).Encode("x") + }) +} diff --git a/testdata/src/default_config/assign/assign.go.golden b/testdata/src/default_config/assign/assign.go.golden index 1239be3..ac24286 100644 --- a/testdata/src/default_config/assign/assign.go.golden +++ b/testdata/src/default_config/assign/assign.go.golden @@ -1,5 +1,10 @@ package testpkg +import ( + "bytes" + "encoding/json" +) + type T struct { I int } @@ -12,6 +17,11 @@ func (*T) Fn() int { return 1 } +func FnC(_ string, fn func() error) error { + fn() + return nil +} + func strictAppend() { s := []string{} s = append(s, "a") @@ -40,3 +50,10 @@ func assignAndCall() { t1.Fn() t2.I = t1.Fn() } + +func closureInCall() { + buf := &bytes.Buffer{} + err := Fnc("buf", func() error { + return json.NewEncoder(buf).Encode("x") + }) +} From de5e4d6b23d09b55304f3b6c2e3e871520693d24 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 21 Jan 2025 23:13:28 +0100 Subject: [PATCH 048/112] Improve defer --- cursor.go | 8 +--- testdata/src/default_config/defer/defer.go | 9 ++++ .../src/default_config/defer/defer.go.golden | 9 ++++ wsl.go | 46 ++++++++++++++----- 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/cursor.go b/cursor.go index f29e156..b21d162 100644 --- a/cursor.go +++ b/cursor.go @@ -133,16 +133,12 @@ func (c *Cursor) PreviousNode() ast.Node { } func (c *Cursor) PeekNext() bool { - reset := c.Save() - defer reset() - + defer c.Save()() return c.Next() } func (c *Cursor) PeekPrevious() bool { - reset := c.Save() - defer reset() - + defer c.Save()() return c.Previous() } diff --git a/testdata/src/default_config/defer/defer.go b/testdata/src/default_config/defer/defer.go index d286bc1..8f3521f 100644 --- a/testdata/src/default_config/defer/defer.go +++ b/testdata/src/default_config/defer/defer.go @@ -2,6 +2,7 @@ package testpkg import ( "fmt" + "os" "sync" ) @@ -58,3 +59,11 @@ func fn2() { _ = a } + +func fn3() { + f, err := os.Open("x") + if err != nil { + panic(err) + } + defer f.Close() +} diff --git a/testdata/src/default_config/defer/defer.go.golden b/testdata/src/default_config/defer/defer.go.golden index a43ac44..64ae67a 100644 --- a/testdata/src/default_config/defer/defer.go.golden +++ b/testdata/src/default_config/defer/defer.go.golden @@ -2,6 +2,7 @@ package testpkg import ( "fmt" + "os" "sync" ) @@ -62,3 +63,11 @@ func fn2() { _ = a } + +func fn3() { + f, err := os.Open("x") + if err != nil { + panic(err) + } + defer f.Close() +} diff --git a/wsl.go b/wsl.go index 2818672..0db3af5 100644 --- a/wsl.go +++ b/wsl.go @@ -205,11 +205,7 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { // 3. With the new check config, one could just disable checks for assign // and it would allow cuddling with anything. if !prevIsValidType && currIsAssign { - currentIdents := allIdents(stmt) - previousIdents := allIdents(previousNode) - intersects := identIntersection(currentIdents, previousIdents) - - if len(intersects) > 0 { + if hasIntersection(stmt, previousNode) { return } } @@ -390,8 +386,27 @@ func (w *WSL) CheckDefer(stmt *ast.DeferStmt, cursor *Cursor) { stmt.Call, cursor, func(n ast.Node) (int, bool) { - _, ok := n.(*ast.DeferStmt) - return 1, !ok + _, previousIsDefer := n.(*ast.DeferStmt) + _, previousIsIf := n.(*ast.IfStmt) + + // We allow defer as a third node only if we if check between. E.g. + // f, err := os.Open(file) + // if err != nil { + // return err + // } + // defer f.Close() + if previousIsIf && w.numberOfStatementsAbove(cursor) >= 2 { + defer cursor.Save()() + + cursor.Previous() + cursor.Previous() + + if hasIntersection(cursor.Stmt(), stmt) { + return 1, false + } + } + + return 1, !previousIsDefer }, CheckDefer, ) @@ -588,11 +603,7 @@ func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { return } - appendIdents := allIdents(appendNode) - previousIdents := allIdents(previousNode) - intersects := identIntersection(appendIdents, previousIdents) - - if len(intersects) == 0 { + if !hasIntersection(appendNode, previousNode) { w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) } } @@ -1029,6 +1040,17 @@ func allIdents(node ast.Node) []*ast.Ident { return idents } +func hasIntersection(a, b ast.Node) bool { + return len(nodeIdentIntersection(a, b)) > 0 +} + +func nodeIdentIntersection(a, b ast.Node) []*ast.Ident { + aI := allIdents(a) + bI := allIdents(b) + + return identIntersection(aI, bI) +} + func identIntersection(a, b []*ast.Ident) []*ast.Ident { intersects := []*ast.Ident{} From 49667d978057ae4a7632c55a8cd0eb46f264b9f2 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 18 Apr 2025 10:10:19 +0200 Subject: [PATCH 049/112] Rework finding idents --- cursor.go | 61 +---------------------- wsl.go | 144 ++++++++++++++++++++++++++++-------------------------- 2 files changed, 78 insertions(+), 127 deletions(-) diff --git a/cursor.go b/cursor.go index b21d162..5190a4a 100644 --- a/cursor.go +++ b/cursor.go @@ -39,65 +39,8 @@ type Cursor struct { // NewCursor creates a new cursor with a given list of statements. func NewCursor(statements []ast.Stmt) *Cursor { return &Cursor{ - currentIdx: -1, - statements: statements, - idents: make(map[string]struct{}), - firstIdents: [][]*ast.Ident{}, - } -} - -// Extend will extend a cursor with another. This is used when returning from a -// check of a nested block to include all scopes recursively. -func (c *Cursor) Extend(otherCursor *Cursor) { - for i := range otherCursor.idents { - c.idents[i] = struct{}{} - } - - c.firstIdents = append(c.firstIdents, otherCursor.firstIdents...) -} - -// Merge a cursor with another. This is used when checking chained blocks such -// as if-else-chains. -func (c *Cursor) Merge(otherCursor *Cursor) { - if len(otherCursor.firstIdents) > 0 { - otherFirstIdents := otherCursor.firstIdents[len(otherCursor.firstIdents)-1] - - if len(c.firstIdents) == 0 { - c.firstIdents = append(c.firstIdents, otherFirstIdents) - } else { - c.firstIdents[0] = append(c.firstIdents[0], otherFirstIdents...) - } - } - - for v := range otherCursor.idents { - c.idents[v] = struct{}{} - } -} - -// Retain will retain the first idents from a Cursor putting them all at the -// first index. This is used when there are multiple blocks at the same level -// with different first identifiers such as if-else. -func (c *Cursor) Retain() { - if len(c.firstIdents) == 0 { - return - } - - for _, firsts := range c.firstIdents[1:] { - c.firstIdents[0] = append(c.firstIdents[0], firsts...) - } - - c.firstIdents = c.firstIdents[:1] -} - -// AddIdents adds all idents in the list to the set of identifiers. If it's the -// first statement for the cursor first identifiers will be extended. -func (c *Cursor) AddIdents(idents []*ast.Ident, addFirst bool) { - for _, i := range idents { - c.idents[i.Name] = struct{}{} - } - - if addFirst { - c.firstIdents = append(c.firstIdents, idents) + currentIdx: -1, + statements: statements, } } diff --git a/wsl.go b/wsl.go index 0db3af5..a054a72 100644 --- a/wsl.go +++ b/wsl.go @@ -53,18 +53,17 @@ func (w *WSL) Run() { } } -func (w *WSL) CheckCuddling(stmt ast.Node, cursor, blockCursor *Cursor, maxAllowedStatements int) { - w.checkCuddlingWithDecl(stmt, cursor, blockCursor, maxAllowedStatements, true) +func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { + w.checkCuddlingWithDecl(stmt, cursor, maxAllowedStatements, true) } -func (w *WSL) CheckCuddlingNoDecl(stmt ast.Node, cursor, blockCursor *Cursor, maxAllowedStatements int) { - w.checkCuddlingWithDecl(stmt, cursor, blockCursor, maxAllowedStatements, false) +func (w *WSL) CheckCuddlingNoDecl(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { + w.checkCuddlingWithDecl(stmt, cursor, maxAllowedStatements, false) } func (w *WSL) checkCuddlingWithDecl( stmt ast.Node, cursor *Cursor, - blockCursor *Cursor, maxAllowedStatements int, declIsValid bool, ) { @@ -113,7 +112,24 @@ func (w *WSL) checkCuddlingWithDecl( // FEATURE: Allow identifier used anywhere in block (including recursive // blocks). if w.Config.AllowWholeBlock { - anyIntersects := identsInMap(previousIdents, blockCursor.idents) + // anyIntersects := identsInMap(previousIdents, blockCursor.idents) + // if len(anyIntersects) > 0 { + // // We have matches, but too many statements above. + // if numStmtsAbove > maxAllowedStatements { + // w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) + // } + + // return + // } + } + + // FEATURE: Allow identifiers used first in block. Configurable to allow + // multiple levels. + if !w.Config.AllowWholeBlock && w.Config.AllowFirstInBlock { + allIdentsInBlock := identsFromNode(stmt) + spew.Dump(allIdentsInBlock) + + anyIntersects := identIntersection(previousIdents, allIdentsInBlock) if len(anyIntersects) > 0 { // We have matches, but too many statements above. if numStmtsAbove > maxAllowedStatements { @@ -122,28 +138,24 @@ func (w *WSL) checkCuddlingWithDecl( return } - } - // FEATURE: Allow identifiers used first in block. Configurable to allow - // multiple levels. - if !w.Config.AllowWholeBlock && w.Config.AllowFirstInBlock { - for i := range w.Config.FirstInBlockMaxDepth { - if i < len(blockCursor.firstIdents) { - firstIntersect := identIntersection( - previousIdents, - blockCursor.firstIdents[i], - ) - - if len(firstIntersect) > 0 { - // We have matches, but too many statements above. - if numStmtsAbove > maxAllowedStatements { - w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) - } - - return - } - } - } + // for i := range w.Config.FirstInBlockMaxDepth { + // if i < len(blockCursor.firstIdents) { + // firstIntersect := identIntersection( + // previousIdents, + // blockCursor.firstIdents[i], + // ) + + // if len(firstIntersect) > 0 { + // // We have matches, but too many statements above. + // if numStmtsAbove > maxAllowedStatements { + // w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) + // } + + // return + // } + // } + // } } // We're cuddled but the line immediately above doesn't contain any @@ -263,29 +275,27 @@ func (w *WSL) checkError( } } -func (w *WSL) CheckBlockAndExtend( +func (w *WSL) MaybeCheckBlock( node ast.Node, blockStmt *ast.BlockStmt, cursor *Cursor, check CheckType, ) { - blockCursor := w.CheckBlock(blockStmt) + w.CheckBlock(blockStmt) if _, ok := w.Config.Checks[check]; ok { - w.CheckCuddling(node, cursor, blockCursor, 1) + w.CheckCuddling(node, cursor, 1) } - - cursor.Extend(blockCursor) } -func (w *WSL) CheckExprAndExtend( +func (w *WSL) MaybeCheckExpr( node ast.Node, expr ast.Expr, cursor *Cursor, predicate func(ast.Node) (int, bool), check CheckType, ) { - maybeBlockCursor := w.CheckExpr(expr, cursor) + w.CheckExpr(expr, cursor) if _, ok := w.Config.Checks[check]; ok { previousNode := cursor.PreviousNode() @@ -295,11 +305,9 @@ func (w *WSL) CheckExprAndExtend( // We don't even have to check if it's actually cuddled or just the previous // one because even if it's not but is a `go` statement it's valid. if n, ok := predicate(previousNode); ok { - w.CheckCuddling(node, cursor, maybeBlockCursor, n) + w.CheckCuddling(node, cursor, n) } } - - cursor.Extend(maybeBlockCursor) } func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { @@ -312,50 +320,47 @@ func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { // if - blockCursor := w.CheckBlock(stmt.Body) + w.CheckBlock(stmt.Body) switch v := stmt.Else.(type) { // else-if case *ast.IfStmt: // We use our new cursor created for this if-block to check nested if. - w.CheckIf(v, blockCursor) - blockCursor.Retain() + w.CheckIf(v, cursor) // else case *ast.BlockStmt: // Merge the idents from the else branch. - blockCursor.Merge(w.CheckBlock(v)) + w.CheckBlock(v) } if _, ok := w.Config.Checks[CheckIf]; ok { - w.CheckCuddling(stmt, cursor, blockCursor, 1) + w.CheckCuddling(stmt, cursor, 1) } - - cursor.Extend(blockCursor) } func (w *WSL) CheckFor(stmt *ast.ForStmt, cursor *Cursor) { - w.CheckBlockAndExtend(stmt, stmt.Body, cursor, CheckFor) + w.MaybeCheckBlock(stmt, stmt.Body, cursor, CheckFor) } func (w *WSL) CheckRange(stmt *ast.RangeStmt, cursor *Cursor) { - w.CheckBlockAndExtend(stmt, stmt.Body, cursor, CheckRange) + w.MaybeCheckBlock(stmt, stmt.Body, cursor, CheckRange) } func (w *WSL) CheckSwitch(stmt *ast.SwitchStmt, cursor *Cursor) { - w.CheckBlockAndExtend(stmt, stmt.Body, cursor, CheckSwitch) + w.MaybeCheckBlock(stmt, stmt.Body, cursor, CheckSwitch) } func (w *WSL) CheckTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { - w.CheckBlockAndExtend(stmt, stmt.Body, cursor, CheckTypeSwitch) + w.MaybeCheckBlock(stmt, stmt.Body, cursor, CheckTypeSwitch) } func (w *WSL) CheckSelect(stmt *ast.SelectStmt, cursor *Cursor) { - w.CheckBlockAndExtend(stmt, stmt.Body, cursor, CheckSelect) + w.MaybeCheckBlock(stmt, stmt.Body, cursor, CheckSelect) } func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { - w.CheckExprAndExtend( + w.MaybeCheckExpr( stmt, stmt.X, cursor, @@ -368,7 +373,7 @@ func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { } func (w *WSL) CheckGo(stmt *ast.GoStmt, cursor *Cursor) { - w.CheckExprAndExtend( + w.MaybeCheckExpr( stmt, stmt.Call, cursor, @@ -381,7 +386,7 @@ func (w *WSL) CheckGo(stmt *ast.GoStmt, cursor *Cursor) { } func (w *WSL) CheckDefer(stmt *ast.DeferStmt, cursor *Cursor) { - w.CheckExprAndExtend( + w.MaybeCheckExpr( stmt, stmt.Call, cursor, @@ -443,7 +448,7 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { return } - w.CheckCuddlingNoDecl(stmt, cursor, &Cursor{}, 1) + w.CheckCuddlingNoDecl(stmt, cursor, 1) } func (w *WSL) CheckBlock(block *ast.BlockStmt) *Cursor { @@ -460,7 +465,7 @@ func (w *WSL) CheckCase(stmt *ast.CaseClause, cursor *Cursor) { w.checkCaseTrailingNewline(stmt.Body, cursor) } - cursor.Extend(w.checkBody(stmt.Body)) + w.checkBody(stmt.Body) } func (w *WSL) CheckComm(stmt *ast.CommClause, cursor *Cursor) { @@ -470,7 +475,7 @@ func (w *WSL) CheckComm(stmt *ast.CommClause, cursor *Cursor) { w.checkCaseTrailingNewline(stmt.Body, cursor) } - cursor.Extend(w.checkBody(stmt.Body)) + w.checkBody(stmt.Body) } func (w *WSL) checkCaseTrailingNewline(body []ast.Stmt, cursor *Cursor) { @@ -610,13 +615,9 @@ func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { func (w *WSL) checkBody(body []ast.Stmt) *Cursor { cursor := NewCursor(body) - isFirst := true for cursor.Next() { w.CheckStmt(cursor.Stmt(), cursor) - - cursor.AddIdents(allIdents(cursor.Stmt()), isFirst) - isFirst = false } return cursor @@ -697,7 +698,7 @@ func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) *Cursor { case *ast.CallExpr: c := w.CheckExpr(s.Fun, cursor) for _, e := range s.Args { - c.Extend(w.CheckExpr(e, cursor)) + w.CheckExpr(e, cursor) } return c @@ -717,7 +718,6 @@ func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) *Cursor { *ast.SliceExpr, *ast.TypeAssertExpr, *ast.UnaryExpr: - cursor.AddIdents(allIdents(s), false) default: fmt.Printf("Not implemented expr: %T\n", s) } @@ -1065,14 +1065,22 @@ func identIntersection(a, b []*ast.Ident) []*ast.Ident { return intersects } -func identsInMap(a []*ast.Ident, m map[string]struct{}) []*ast.Ident { - intersects := []*ast.Ident{} +func identsFromNode(node ast.Node) []*ast.Ident { + var ( + idents []*ast.Ident + seen = map[string]struct{}{} + ) - for _, as := range a { - if _, ok := m[as.Name]; ok { - intersects = append(intersects, as) + ast.Inspect(node, func(n ast.Node) bool { + if ident, ok := n.(*ast.Ident); ok { + if _, exists := seen[ident.Name]; !exists { + seen[ident.Name] = struct{}{} + idents = append(idents, ident) + } } - } - return intersects + return true + }) + + return idents } From f803fcfd1a5fe8350d65fe39a9019651da624494 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 18 Apr 2025 11:32:55 +0200 Subject: [PATCH 050/112] Drop `FirstInBlockMaxDepth` + fixes --- config.go | 11 ++- cursor.go | 19 ----- testdata/src/default_config/assign/assign.go | 8 ++ .../default_config/assign/assign.go.golden | 9 +++ .../default_config/type_switch/type_switch.go | 3 +- .../type_switch/type_switch.go.golden | 3 +- .../with_config/first_in_block_n5/block.go | 31 ------- .../first_in_block_n5/block.go.golden | 33 -------- wsl.go | 81 +++++++++---------- wsl_test.go | 8 -- 10 files changed, 64 insertions(+), 142 deletions(-) delete mode 100644 testdata/src/with_config/first_in_block_n5/block.go delete mode 100644 testdata/src/with_config/first_in_block_n5/block.go.golden diff --git a/config.go b/config.go index 73db832..9ffb29d 100644 --- a/config.go +++ b/config.go @@ -67,12 +67,11 @@ type Configuration struct { func NewConfig() *Configuration { return &Configuration{ - AllowFirstInBlock: true, - AllowWholeBlock: false, - FirstInBlockMaxDepth: 1, - CaseMaxLines: 0, - ReturnMaxLines: 2, - Checks: DefaultChecks(), + AllowFirstInBlock: true, + AllowWholeBlock: false, + CaseMaxLines: 0, + ReturnMaxLines: 2, + Checks: DefaultChecks(), } } diff --git a/cursor.go b/cursor.go index 5190a4a..bd6d6fb 100644 --- a/cursor.go +++ b/cursor.go @@ -10,25 +10,6 @@ var ErrCursourOutObFounds = errors.New("out of bounds") // Cursor holds a list of statements and a pointer to where in the list we are. // Each block gets a new cursor and can be used to check previous or coming // statements. -// -// A cursor also keeps a set of all identifiers seen in the block and child -// blocks and a separate list of string slices of first identifier in blocks. -// Each element represents the depth of where the identifiers was found. -// -// In this example: -// -// func Fn() { -// if true { -// a, b := 1, 2 -// if false { -// c := 3 -// d := 4 -// } -// } -// } -// -// idents will be a map containing [a, b, c, d] and first identifiers will be -// [[a, b], [c]]. type Cursor struct { currentIdx int statements []ast.Stmt diff --git a/testdata/src/default_config/assign/assign.go b/testdata/src/default_config/assign/assign.go index 1175b54..6c41b0b 100644 --- a/testdata/src/default_config/assign/assign.go +++ b/testdata/src/default_config/assign/assign.go @@ -56,3 +56,11 @@ func closureInCall() { return json.NewEncoder(buf).Encode("x") }) } + +func assignAfterBlock() { + x := 1 + if x > 0 { + return + } + x = 2 // want "missing whitespace decreases readability" +} diff --git a/testdata/src/default_config/assign/assign.go.golden b/testdata/src/default_config/assign/assign.go.golden index ac24286..7d8c566 100644 --- a/testdata/src/default_config/assign/assign.go.golden +++ b/testdata/src/default_config/assign/assign.go.golden @@ -57,3 +57,12 @@ func closureInCall() { return json.NewEncoder(buf).Encode("x") }) } + +func assignAfterBlock() { + x := 1 + if x > 0 { + return + } + + x = 2 // want "missing whitespace decreases readability" +} diff --git a/testdata/src/default_config/type_switch/type_switch.go b/testdata/src/default_config/type_switch/type_switch.go index c4e07b0..36dc932 100644 --- a/testdata/src/default_config/type_switch/type_switch.go +++ b/testdata/src/default_config/type_switch/type_switch.go @@ -15,8 +15,7 @@ func fn1() { } func fn2() { - var a any - a = 1 + var a any = 1 b := 1 switch a.(type) { // want "missing whitespace decreases readability" diff --git a/testdata/src/default_config/type_switch/type_switch.go.golden b/testdata/src/default_config/type_switch/type_switch.go.golden index 63a3f1c..97c5f6e 100644 --- a/testdata/src/default_config/type_switch/type_switch.go.golden +++ b/testdata/src/default_config/type_switch/type_switch.go.golden @@ -15,8 +15,7 @@ func fn1() { } func fn2() { - var a any - a = 1 + var a any = 1 b := 1 diff --git a/testdata/src/with_config/first_in_block_n5/block.go b/testdata/src/with_config/first_in_block_n5/block.go deleted file mode 100644 index 2a07ca9..0000000 --- a/testdata/src/with_config/first_in_block_n5/block.go +++ /dev/null @@ -1,31 +0,0 @@ -package testpkg - -func Fn(_ int) {} - -func fn1() { - one := 1 - two := 2 - three := 3 - - a := 1 - if true { - Fn(a) - } - - b := 1 - if true { - Fn(one) - - if false { - Fn(two) - - if true { - Fn(three) - - if true { - Fn(b) - } - } - } - } -} diff --git a/testdata/src/with_config/first_in_block_n5/block.go.golden b/testdata/src/with_config/first_in_block_n5/block.go.golden deleted file mode 100644 index 46b7e88..0000000 --- a/testdata/src/with_config/first_in_block_n5/block.go.golden +++ /dev/null @@ -1,33 +0,0 @@ -package testpkg - -func Fn(_ int) {} - -func fn1() { - one := 1 - two := 2 - three := 3 - - a := 1 - if true { - Fn(a) - } - - b := 1 - - if true { - Fn(one) - - if false { - Fn(two) - - if true { - Fn(three) - - if true { - Fn(b) - } - } - } - } -} - diff --git a/wsl.go b/wsl.go index a054a72..9269c17 100644 --- a/wsl.go +++ b/wsl.go @@ -53,16 +53,26 @@ func (w *WSL) Run() { } } +func (w *WSL) CheckCuddlingBlock(stmt ast.Node, blockList []ast.Stmt, cursor *Cursor, maxAllowedStatements int) { + var firstBlockStmt ast.Node + if len(blockList) > 0 { + firstBlockStmt = blockList[0] + } + + w.checkCuddlingWithDecl(stmt, firstBlockStmt, cursor, maxAllowedStatements, true) +} + func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { - w.checkCuddlingWithDecl(stmt, cursor, maxAllowedStatements, true) + w.checkCuddlingWithDecl(stmt, nil, cursor, maxAllowedStatements, true) } func (w *WSL) CheckCuddlingNoDecl(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { - w.checkCuddlingWithDecl(stmt, cursor, maxAllowedStatements, false) + w.checkCuddlingWithDecl(stmt, nil, cursor, maxAllowedStatements, false) } func (w *WSL) checkCuddlingWithDecl( stmt ast.Node, + firstBlockStmt ast.Node, cursor *Cursor, maxAllowedStatements int, declIsValid bool, @@ -112,50 +122,33 @@ func (w *WSL) checkCuddlingWithDecl( // FEATURE: Allow identifier used anywhere in block (including recursive // blocks). if w.Config.AllowWholeBlock { - // anyIntersects := identsInMap(previousIdents, blockCursor.idents) - // if len(anyIntersects) > 0 { - // // We have matches, but too many statements above. - // if numStmtsAbove > maxAllowedStatements { - // w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) - // } + allIdentsInBlock := identsFromNode(stmt) + + anyIntersects := identIntersection(previousIdents, allIdentsInBlock) + if len(anyIntersects) > 0 { + // We have matches, but too many statements above. + if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { + w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) + } - // return - // } + return + } } // FEATURE: Allow identifiers used first in block. Configurable to allow // multiple levels. if !w.Config.AllowWholeBlock && w.Config.AllowFirstInBlock { - allIdentsInBlock := identsFromNode(stmt) - spew.Dump(allIdentsInBlock) + firstStmtIdents := identsFromNode(firstBlockStmt) - anyIntersects := identIntersection(previousIdents, allIdentsInBlock) + anyIntersects := identIntersection(previousIdents, firstStmtIdents) if len(anyIntersects) > 0 { // We have matches, but too many statements above. - if numStmtsAbove > maxAllowedStatements { + if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) } return } - - // for i := range w.Config.FirstInBlockMaxDepth { - // if i < len(blockCursor.firstIdents) { - // firstIntersect := identIntersection( - // previousIdents, - // blockCursor.firstIdents[i], - // ) - - // if len(firstIntersect) > 0 { - // // We have matches, but too many statements above. - // if numStmtsAbove > maxAllowedStatements { - // w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) - // } - - // return - // } - // } - // } } // We're cuddled but the line immediately above doesn't contain any @@ -216,8 +209,12 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { // whatever) or we don't. // 3. With the new check config, one could just disable checks for assign // and it would allow cuddling with anything. + // + // This is also a bit odd because the reason it works in the current impl. + // is because we only allow this if the line above is a single line + // statement which it never is for e.g. if. if !prevIsValidType && currIsAssign { - if hasIntersection(stmt, previousNode) { + if _, ok := previousNode.(*ast.ExprStmt); ok && hasIntersection(stmt, previousNode) { return } } @@ -284,7 +281,7 @@ func (w *WSL) MaybeCheckBlock( w.CheckBlock(blockStmt) if _, ok := w.Config.Checks[check]; ok { - w.CheckCuddling(node, cursor, 1) + w.CheckCuddlingBlock(node, blockStmt.List, cursor, 1) } } @@ -318,24 +315,22 @@ func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { w.CheckBlock(funcDecl.Body) } -func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor) { +func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor, isElse bool) { // if w.CheckBlock(stmt.Body) switch v := stmt.Else.(type) { // else-if case *ast.IfStmt: - // We use our new cursor created for this if-block to check nested if. - w.CheckIf(v, cursor) + w.CheckIf(v, cursor, true) // else case *ast.BlockStmt: - // Merge the idents from the else branch. w.CheckBlock(v) } - if _, ok := w.Config.Checks[CheckIf]; ok { - w.CheckCuddling(stmt, cursor, 1) + if _, ok := w.Config.Checks[CheckIf]; !isElse && ok { + w.CheckCuddlingBlock(stmt, stmt.Body.List, cursor, 1) } } @@ -628,7 +623,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { switch s := stmt.(type) { // if a {} else if b {} else {} case *ast.IfStmt: - w.CheckIf(s, cursor) + w.CheckIf(s, cursor, false) // for {} / for a; b; c {} case *ast.ForStmt: w.CheckFor(s, cursor) @@ -1071,6 +1066,10 @@ func identsFromNode(node ast.Node) []*ast.Ident { seen = map[string]struct{}{} ) + if node == nil { + return idents + } + ast.Inspect(node, func(n ast.Node) bool { if ident, ok := n.(*ast.Ident); ok { if _, exists := seen[ident.Name]; !exists { diff --git a/wsl_test.go b/wsl_test.go index 1cb999d..03217a6 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -53,14 +53,6 @@ func TestWithConfig(t *testing.T) { subdir: "first_in_block_n1", configFn: func(config *Configuration) { config.AllowFirstInBlock = true - config.FirstInBlockMaxDepth = 1 - }, - }, - { - subdir: "first_in_block_n5", - configFn: func(config *Configuration) { - config.AllowFirstInBlock = true - config.FirstInBlockMaxDepth = 5 }, }, { From be354248cd30ff6604fde226a56ff2103ccd5866 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 18 Apr 2025 11:38:51 +0200 Subject: [PATCH 051/112] Update golangci-lint and fix issues --- .golangci.yml | 81 ++++++++++++++++++++++++++++----------------------- analyzer.go | 2 +- cursor.go | 6 ++-- wsl.go | 20 +++++-------- 4 files changed, 55 insertions(+), 54 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 4966964..8e7f879 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,42 +1,14 @@ --- -run: - timeout: 1m - issues-exit-code: 1 - tests: true +version: "2" output: - print-issued-lines: false - sort-results: true formats: - - format: colored-line-number - -linters-settings: - gocognit: - min-complexity: 10 - - depguard: - rules: - main: - deny: - - pkg: "github.com/davecgh/go-spew/spew" - desc: not allowed - - misspell: - locale: US - - gocritic: - # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` - # to see all tags and checks. Empty list by default. See - # https://github.com/go-critic/go-critic#usage -> section "Tags". - enabled-tags: - - diagnostic - - experimental - - opinionated - - performance - - style + text: + path: stdout + print-issued-lines: false linters: - enable-all: true + default: all disable: - cyclop - depguard @@ -44,16 +16,14 @@ linters: - dupword - err113 - exhaustruct - - exportloopref - forbidigo - funlen - - gci - gocognit - gocyclo - godox - - mnd - lll - maintidx + - mnd - nakedret - nestif - nlreturn @@ -65,8 +35,45 @@ linters: - varnamelen - wastedassign + settings: + depguard: + rules: + main: + deny: + - pkg: github.com/davecgh/go-spew/spew + desc: not allowed + gocognit: + min-complexity: 10 + gocritic: + # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` + # to see all tags and checks. Empty list by default. See + # https://github.com/go-critic/go-critic#usage -> section "Tags". + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + misspell: + locale: US + + exclusions: + presets: + - comments + - common-false-positives + - std-error-handling + issues: - exclude-use-default: true max-issues-per-linter: 0 max-same-issues: 0 +formatters: + enable: + - gofmt + - gofumpt + - goimports + settings: + gofmt: + rewrite-rules: + - pattern: "interface{}" + replacement: "any" # vim: set sw=2 ts=2 et: diff --git a/analyzer.go b/analyzer.go index 9e278dd..9685eb1 100644 --- a/analyzer.go +++ b/analyzer.go @@ -56,7 +56,7 @@ func (wa *wslAnalyzer) flags() flag.FlagSet { return *flags } -func (wa *wslAnalyzer) run(pass *analysis.Pass) (interface{}, error) { +func (wa *wslAnalyzer) run(pass *analysis.Pass) (any, error) { if err := wa.config.Update(wa.enableAll, wa.disableAll, wa.enable, wa.disable); err != nil { return nil, err } diff --git a/cursor.go b/cursor.go index bd6d6fb..e9b3ea0 100644 --- a/cursor.go +++ b/cursor.go @@ -11,10 +11,8 @@ var ErrCursourOutObFounds = errors.New("out of bounds") // Each block gets a new cursor and can be used to check previous or coming // statements. type Cursor struct { - currentIdx int - statements []ast.Stmt - idents map[string]struct{} - firstIdents [][]*ast.Ident + currentIdx int + statements []ast.Stmt } // NewCursor creates a new cursor with a given list of statements. diff --git a/wsl.go b/wsl.go index 9269c17..9dccd44 100644 --- a/wsl.go +++ b/wsl.go @@ -684,23 +684,22 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { } } -func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) *Cursor { +func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) { switch s := expr.(type) { // func() {} case *ast.FuncLit: - return w.CheckBlock(s.Body) + w.CheckBlock(s.Body) // Call(args...) case *ast.CallExpr: - c := w.CheckExpr(s.Fun, cursor) + w.CheckExpr(s.Fun, cursor) + for _, e := range s.Args { w.CheckExpr(e, cursor) } - - return c case *ast.IndexExpr: - return w.CheckExpr(s.X, cursor) + w.CheckExpr(s.X, cursor) case *ast.StarExpr: - return w.CheckExpr(s.X, cursor) + w.CheckExpr(s.X, cursor) case *ast.ArrayType, *ast.BasicLit, *ast.BinaryExpr, @@ -716,8 +715,6 @@ func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) *Cursor { default: fmt.Printf("Not implemented expr: %T\n", s) } - - return NewCursor([]ast.Stmt{}) } // numberOfStatementsAbove will find out how many lines above the cursor's @@ -944,8 +941,7 @@ func allIdents(node ast.Node) []*ast.Ident { case *ast.IfStmt: idents = append(idents, allIdents(n.Init)...) idents = append(idents, allIdents(n.Cond)...) - // TODO - // idents = append(idents, allIdents(n.Else)...) + idents = append(idents, allIdents(n.Else)...) case *ast.BinaryExpr: idents = append(idents, allIdents(n.X)...) idents = append(idents, allIdents(n.Y)...) @@ -1073,8 +1069,8 @@ func identsFromNode(node ast.Node) []*ast.Ident { ast.Inspect(node, func(n ast.Node) bool { if ident, ok := n.(*ast.Ident); ok { if _, exists := seen[ident.Name]; !exists { - seen[ident.Name] = struct{}{} idents = append(idents, ident) + seen[ident.Name] = struct{}{} } } From d02bd7f842bc7cf382baa25170da506a0f9a85a8 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 18 Apr 2025 12:37:51 +0200 Subject: [PATCH 052/112] Remove `allIdents`, support disable IncDec --- config.go | 15 +++-- wsl.go | 197 +++++++++--------------------------------------------- 2 files changed, 39 insertions(+), 173 deletions(-) diff --git a/config.go b/config.go index 9ffb29d..d0ab71b 100644 --- a/config.go +++ b/config.go @@ -28,6 +28,7 @@ const ( CheckFor CheckGo CheckIf + CheckIncDec CheckLabel CheckLeadingWhitespace CheckTrailingWhitespace @@ -57,12 +58,11 @@ Configuration migration - IncludeGenerated | Done. */ type Configuration struct { - AllowFirstInBlock bool - AllowWholeBlock bool - FirstInBlockMaxDepth int - CaseMaxLines int - ReturnMaxLines int - Checks CheckSet + AllowFirstInBlock bool + AllowWholeBlock bool + CaseMaxLines int + ReturnMaxLines int + Checks CheckSet } func NewConfig() *Configuration { @@ -126,6 +126,7 @@ func DefaultChecks() CheckSet { CheckFor: {}, CheckGo: {}, CheckIf: {}, + CheckIncDec: {}, CheckLabel: {}, CheckLeadingWhitespace: {}, CheckTrailingWhitespace: {}, @@ -181,6 +182,8 @@ func CheckFromString(s string) (CheckType, error) { return CheckGo, nil case "if": return CheckIf, nil + case "incdec": + return CheckIncDec, nil case "leading-whitespace": return CheckLeadingWhitespace, nil case "trailing-whitespace": diff --git a/wsl.go b/wsl.go index 9dccd44..eb642e0 100644 --- a/wsl.go +++ b/wsl.go @@ -7,7 +7,6 @@ import ( "go/types" "slices" - "github.com/davecgh/go-spew/spew" "golang.org/x/tools/go/analysis" ) @@ -79,14 +78,14 @@ func (w *WSL) checkCuddlingWithDecl( ) { defer cursor.Save()() - currentIdents := allIdents(cursor.Stmt()) + currentIdents := identsFromNode(cursor.Stmt(), true) previousIdents := []*ast.Ident{} var previousNode ast.Node if cursor.Previous() { previousNode = cursor.Stmt() - previousIdents = allIdents(previousNode) + previousIdents = identsFromNode(previousNode, true) cursor.Next() // Move forward again } @@ -119,10 +118,9 @@ func (w *WSL) checkCuddlingWithDecl( return } - // FEATURE: Allow identifier used anywhere in block (including recursive - // blocks). + // FEATURE(AllowWholeBlock): Allow identifier used anywhere in block (including recursive blocks). if w.Config.AllowWholeBlock { - allIdentsInBlock := identsFromNode(stmt) + allIdentsInBlock := identsFromNode(stmt, false) anyIntersects := identIntersection(previousIdents, allIdentsInBlock) if len(anyIntersects) > 0 { @@ -135,10 +133,9 @@ func (w *WSL) checkCuddlingWithDecl( } } - // FEATURE: Allow identifiers used first in block. Configurable to allow - // multiple levels. + // FEATURE(AllowFirstInBlock): Allow identifiers used first in block. if !w.Config.AllowWholeBlock && w.Config.AllowFirstInBlock { - firstStmtIdents := identsFromNode(firstBlockStmt) + firstStmtIdents := identsFromNode(firstBlockStmt, true) anyIntersects := identIntersection(previousIdents, firstStmtIdents) if len(anyIntersects) > 0 { @@ -159,13 +156,13 @@ func (w *WSL) checkCuddlingWithDecl( return } - // Check the maximum number of allowed statements above, and if not - // disabled (-1) check that the previous one intersects with the - // current one. - if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { - // Idents on the line above exist in the current condition so that - // should remain cuddled. - if len(intersects) > 0 { + // Idents on the line above exist in the current condition so that should + // remain cuddled. + if len(intersects) > 0 { + // Check the maximum number of allowed statements above, and if not + // disabled (-1) check that the previous one intersects with the current + // one. + if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) } } @@ -297,10 +294,6 @@ func (w *WSL) MaybeCheckExpr( if _, ok := w.Config.Checks[check]; ok { previousNode := cursor.PreviousNode() - // We can cuddle any amount `go` statements so only check cuddling if the - // previous one isn't a `go` call. - // We don't even have to check if it's actually cuddled or just the previous - // one because even if it's not but is a `go` statement it's valid. if n, ok := predicate(previousNode); ok { w.CheckCuddling(node, cursor, n) } @@ -372,6 +365,10 @@ func (w *WSL) CheckGo(stmt *ast.GoStmt, cursor *Cursor) { stmt, stmt.Call, cursor, + // We can cuddle any amount `go` statements so only check cuddling if the + // previous one isn't a `go` call. + // We don't even have to check if it's actually cuddled or just the previous + // one because even if it's not but is a `go` statement it's valid. func(n ast.Node) (int, bool) { _, ok := n.(*ast.GoStmt) return 1, !ok @@ -559,6 +556,10 @@ func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { func (w *WSL) CheckIncDec(stmt *ast.IncDecStmt, cursor *Cursor) { defer w.CheckExpr(stmt.X, cursor) + if _, ok := w.Config.Checks[CheckIncDec]; !ok { + return + } + w.CheckCuddlingWithoutIntersection(stmt, cursor) } @@ -887,157 +888,13 @@ func (w *WSL) addError(report, start, end token.Pos, message string) { w.Issues[report] = issue } -func allIdents(node ast.Node) []*ast.Ident { - idents := []*ast.Ident{} - - if node == nil { - return idents - } - - switch n := node.(type) { - case *ast.Ident: - return []*ast.Ident{n} - case *ast.ExprStmt: - idents = append(idents, allIdents(n.X)...) - case *ast.DeclStmt: - idents = append(idents, allIdents(n.Decl)...) - case *ast.GenDecl: - for _, spec := range n.Specs { - idents = append(idents, allIdents(spec)...) - } - case *ast.GoStmt: - idents = append(idents, allIdents(n.Call)...) - case *ast.DeferStmt: - idents = append(idents, allIdents(n.Call)...) - case *ast.ValueSpec: - for _, name := range n.Names { - idents = append(idents, allIdents(name)...) - } - - for _, value := range n.Values { - idents = append(idents, allIdents(value)...) - } - case *ast.AssignStmt: - // NOTE: For TypeSwitchStatements, this can be a false positive by - // allowing shadowing and "tricking" usage; - // - // var v any - // - // notV := 1 - // switch notV := v.(type) {} - // - // This would trick wsl to see `notV` used in both type switch and on - // line above - faulty(?) - for _, lhs := range n.Lhs { - idents = append(idents, allIdents(lhs)...) - } - - // This must be here to see if a variable is used on the RHS, e.g. - // a := 1 - // b = append(b, fmt.Sprintf("%s", a)) - for _, rhs := range n.Rhs { - idents = append(idents, allIdents(rhs)...) - } - case *ast.IfStmt: - idents = append(idents, allIdents(n.Init)...) - idents = append(idents, allIdents(n.Cond)...) - idents = append(idents, allIdents(n.Else)...) - case *ast.BinaryExpr: - idents = append(idents, allIdents(n.X)...) - idents = append(idents, allIdents(n.Y)...) - case *ast.RangeStmt: - idents = append(idents, allIdents(n.X)...) - case *ast.SelectorExpr: - idents = append(idents, allIdents(n.X)...) - case *ast.UnaryExpr: - idents = append(idents, allIdents(n.X)...) - case *ast.ForStmt: - idents = append(idents, allIdents(n.Init)...) - idents = append(idents, allIdents(n.Cond)...) - idents = append(idents, allIdents(n.Post)...) - case *ast.SwitchStmt: - idents = append(idents, allIdents(n.Init)...) - idents = append(idents, allIdents(n.Tag)...) - case *ast.TypeSwitchStmt: - idents = append(idents, allIdents(n.Init)...) - idents = append(idents, allIdents(n.Assign)...) - case *ast.TypeAssertExpr: - idents = append(idents, allIdents(n.X)...) - case *ast.CallExpr: - idents = append(idents, allIdents(n.Fun)...) - for _, arg := range n.Args { - idents = append(idents, allIdents(arg)...) - } - case *ast.CompositeLit: - for _, elt := range n.Elts { - idents = append(idents, allIdents(elt)...) - } - case *ast.IncDecStmt: - idents = allIdents(n.X) - case *ast.CaseClause: - for _, expr := range n.List { - idents = append(idents, allIdents(expr)...) - } - case *ast.ReturnStmt: - for _, r := range n.Results { - idents = append(idents, allIdents(r)...) - } - case *ast.KeyValueExpr: - idents = append(idents, allIdents(n.Key)...) - idents = append(idents, allIdents(n.Value)...) - case *ast.MapType: - idents = append(idents, allIdents(n.Key)...) - idents = append(idents, allIdents(n.Value)...) - case *ast.StarExpr: - idents = append(idents, allIdents(n.X)...) - case *ast.IndexExpr: - idents = append(idents, allIdents(n.X)...) - idents = append(idents, allIdents(n.Index)...) - case *ast.SliceExpr: - idents = append(idents, allIdents(n.X)...) - idents = append(idents, allIdents(n.Low)...) - idents = append(idents, allIdents(n.High)...) - idents = append(idents, allIdents(n.Max)...) - case *ast.FieldList: - for _, f := range n.List { - idents = append(idents, allIdents(f)...) - } - case *ast.StructType: - idents = append(idents, allIdents(n.Fields)...) - case *ast.ParenExpr: - idents = append(idents, allIdents(n.X)...) - case *ast.SendStmt: - idents = append(idents, allIdents(n.Chan)...) - idents = append(idents, allIdents(n.Value)...) - case *ast.ChanType: - idents = append(idents, allIdents(n.Value)...) - case *ast.CommClause: - for _, s := range n.Body { - idents = append(idents, allIdents(s)...) - } - case *ast.ArrayType, - *ast.BasicLit, - *ast.BlockStmt, - *ast.BranchStmt, - *ast.FuncLit, - *ast.LabeledStmt, - *ast.SelectStmt, - *ast.TypeSpec: - default: - spew.Dump(node) - fmt.Printf("missing ident detection for %T\n", node) - } - - return idents -} - func hasIntersection(a, b ast.Node) bool { return len(nodeIdentIntersection(a, b)) > 0 } func nodeIdentIntersection(a, b ast.Node) []*ast.Ident { - aI := allIdents(a) - bI := allIdents(b) + aI := identsFromNode(a, true) + bI := identsFromNode(b, true) return identIntersection(aI, bI) } @@ -1056,7 +913,7 @@ func identIntersection(a, b []*ast.Ident) []*ast.Ident { return intersects } -func identsFromNode(node ast.Node) []*ast.Ident { +func identsFromNode(node ast.Node, skipBlock bool) []*ast.Ident { var ( idents []*ast.Ident seen = map[string]struct{}{} @@ -1067,6 +924,12 @@ func identsFromNode(node ast.Node) []*ast.Ident { } ast.Inspect(node, func(n ast.Node) bool { + if skipBlock { + if _, ok := n.(*ast.BlockStmt); ok { + return false + } + } + if ident, ok := n.(*ast.Ident); ok { if _, exists := seen[ident.Name]; !exists { idents = append(idents, ident) From 5defbd7e4f8d7a3168a985799314e9b02559a3c9 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 18 Apr 2025 12:39:01 +0200 Subject: [PATCH 053/112] Update dependencies --- go.mod | 9 +++++---- go.sum | 14 ++++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index a72643e..b881d50 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,16 @@ module github.com/bombsimon/wsl/v4 -go 1.23 +go 1.24 require ( - github.com/davecgh/go-spew v1.1.1 github.com/stretchr/testify v1.9.0 - golang.org/x/tools v0.19.0 + golang.org/x/tools v0.32.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/mod v0.16.0 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/sync v0.13.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index cbec71b..b2e79ca 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,17 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From 117c700d8e5aaa1c58d682b2708cb54f6af6b73a Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 18 Apr 2025 13:32:15 +0200 Subject: [PATCH 054/112] Add test with all checks disabled --- .../src/with_config/disable_all/disable.go | 167 ++++++++++++++++++ wsl_test.go | 6 + 2 files changed, 173 insertions(+) create mode 100644 testdata/src/with_config/disable_all/disable.go diff --git a/testdata/src/with_config/disable_all/disable.go b/testdata/src/with_config/disable_all/disable.go new file mode 100644 index 0000000..9287837 --- /dev/null +++ b/testdata/src/with_config/disable_all/disable.go @@ -0,0 +1,167 @@ +package testpkg + +func assign() { + var a = 1 + b := 1 + + _ = a + _ = b +} + +func branch() { + for i := range(make(int[], 2)) { + _ = 1 + _ = 1 + _ = 1 + continue + } + + for i := range(make(int[], 2)) { + _ = 1 + _ = 1 + _ = 1 + break + } +} + +func decl() { + var a = 1 + var b = 1 + + _ = a + _ = b +} + +func defer() { + d := 1 + defer func() {}() +} + +func expr() { + a := 1 + b := 2 + fmt.Println("") + c := 3 + d := 4 +} + +func for() { + a := 1 + b := 2 + for i := 0; i < b; i++ { + panic(1) + } + + for i := 0; i < 1; i++ { + panic("") + } + for i := 0; i < 1; i++ { + panic("") + } + + _ = a + _ = b +} + +func go() { + f1 := func() {} + + f2 := func() {} + go f1() + + f3 := func() {} + go Fn(f1) +} + +func if() { + a := 1 + b := 2 + if b == 2 { + panic(1) + } + + b = 2 + a = 1 + if b == 2 { + panic(1) + } + + _ = a + _ = b +} + +func label() { +L1: + if true { + _ = 1 + } +L2: + if true { + _ = 1 + } +} + +func range() { + a := []int{} + b := []int{} + for range b { + panic(1) + } + + _ = a + _ = b +} + +func return() { + if true { + _ = 1 + _ = 2 + return 1 + } + return 1 +} + +func select(ctx context.Context, ch1 chan struct{}) { + x := 1 + select { + case ctx.Done(): + _ = 1 + case <-ch1: + _ = 1 + } + + _ = x +} + +func switch() { + a := 1 + b := 2 + switch b { + case 1: + case 2: + case 3: + panic(a) + } +} + +func typeSwitch() { + var a any = 1 + + b := 1 + switch a.(type) { + case int: + case string: + } + + _ = b +} + +func whitespace() { + + if true { + + _ = 1 + + } + +} diff --git a/wsl_test.go b/wsl_test.go index 03217a6..16cdc56 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -67,6 +67,12 @@ func TestWithConfig(t *testing.T) { config.Checks.Add(CheckAssignExclusive) }, }, + { + subdir: "disable_all", + configFn: func(config *Configuration) { + config.Checks = NoChecks() + }, + }, } { t.Run(tc.subdir, func(t *testing.T) { config := NewConfig() From 0df5e56c4df2f7f95377fa1c691bb3ebc3ae5a77 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 18 Apr 2025 13:34:54 +0200 Subject: [PATCH 055/112] Update github actions --- .github/workflows/go.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index bde69c2..f43652c 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -2,6 +2,8 @@ name: CI on: push: + branches: + - master pull_request: jobs: @@ -13,9 +15,9 @@ jobs: - uses: actions/setup-go@v5 with: go-version: stable - - uses: golangci/golangci-lint-action@v6 + - uses: golangci/golangci-lint-action@v7 with: - version: v1.62.2 + version: v2.0.2 tests: # run after golangci-lint action to not produce duplicated errors From 3e5c5fc70d2b17390e6a5b0763b013e6bd9f491f Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 18 Apr 2025 13:46:36 +0200 Subject: [PATCH 056/112] Skip cgo files --- analyzer.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/analyzer.go b/analyzer.go index 9685eb1..b89c1e9 100644 --- a/analyzer.go +++ b/analyzer.go @@ -24,8 +24,9 @@ func NewAnalyzer(config *Configuration) *analysis.Analyzer { // wslAnalyzer is a wrapper around the configuration which is used to be able to // set the configuration when creating the analyzer and later be able to update // flags and running method. +// // TODO: This should be a public configuration you can pass to `NewAnalyzer` to -// enable `goalngci-lint` to pass `includeGenerated` but also pass a simpler +// enable `golang-lint` to pass `includeGenerated` but also pass a simpler // enable/disable list of strings. type wslAnalyzer struct { config *Configuration @@ -71,10 +72,16 @@ func (wa *wslAnalyzer) run(pass *analysis.Pass) (any, error) { // is a not a '.go' file. unadjustedFilename := pass.Fset.PositionFor(file.Pos(), false).Filename + // if the file is related to cgo the filename of the unadjusted position + // is a not a '.go' file. + if !strings.HasSuffix(unadjustedFilename, ".go") { + continue + } + // The file is skipped if the "unadjusted" file is a Go file, and it's a // generated file (ex: "_test.go" file). The other non-Go files are // skipped by the first 'if' with the adjusted position. - if !wa.includeGenerated && ast.IsGenerated(file) && strings.HasSuffix(unadjustedFilename, ".go") { + if !wa.includeGenerated && ast.IsGenerated(file) { continue } @@ -122,7 +129,7 @@ type multiStringValue struct { func (m *multiStringValue) Set(value string) error { var s []string - for _, v := range strings.Split(value, ",") { + for v := range strings.SplitSeq(value, ",") { s = append(s, strings.TrimSpace(v)) } From ece389938a52feb34cc0d579c27cf9bc0d93fb04 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 21 Apr 2025 19:30:46 +0200 Subject: [PATCH 057/112] Comments and cleanup --- analyzer.go | 2 +- config.go | 32 +++++++++++++++++++++++++++----- cursor.go | 13 ------------- wsl.go | 23 +++++++++++++---------- 4 files changed, 41 insertions(+), 29 deletions(-) diff --git a/analyzer.go b/analyzer.go index b89c1e9..53337e4 100644 --- a/analyzer.go +++ b/analyzer.go @@ -138,7 +138,7 @@ func (m *multiStringValue) Set(value string) error { return nil } -// Set implements the flag.Value interface. +// String implements the flag.Value interface. func (m *multiStringValue) String() string { if m.slicePtr == nil { return "" diff --git a/config.go b/config.go index d0ab71b..63c90f2 100644 --- a/config.go +++ b/config.go @@ -14,29 +14,51 @@ type CheckType int // Each checker is represented by a CheckType that is used to enable or disable // the check. +// A check can either be of a specific built-in keyword or custom checks. const ( CheckInvalid CheckType = iota CheckAssign - CheckAssignExclusive - CheckAppend CheckBreak CheckContinue CheckDecl CheckDefer CheckExpr - CheckErr CheckFor CheckGo CheckIf CheckIncDec CheckLabel - CheckLeadingWhitespace - CheckTrailingWhitespace CheckRange CheckReturn CheckSelect CheckSwitch CheckTypeSwitch + + // Assign exclusive only allows assignments of either new variables or + // re-assignment of existing ones, e.g. + // + // a := 1 + // b := 2 + // + // a = 1 + // b = 2 + CheckAssignExclusive + // Check append only allows assignments of `append` to be cuddled with other + // assignments if it's a variable used in the append statement, e.g. + // + // a := 1 + // x = append(x, a) + CheckAppend + // Force error checking to follow immediately after an error variable is + // assigned, e.g. + // + // _, err := someFn() + // if err != nil { + // panic(err) + // } + CheckErr + CheckLeadingWhitespace + CheckTrailingWhitespace ) /* diff --git a/cursor.go b/cursor.go index e9b3ea0..71bfc65 100644 --- a/cursor.go +++ b/cursor.go @@ -1,12 +1,9 @@ package wsl import ( - "errors" "go/ast" ) -var ErrCursourOutObFounds = errors.New("out of bounds") - // Cursor holds a list of statements and a pointer to where in the list we are. // Each block gets a new cursor and can be used to check previous or coming // statements. @@ -54,16 +51,6 @@ func (c *Cursor) PreviousNode() ast.Node { return previousNode } -func (c *Cursor) PeekNext() bool { - defer c.Save()() - return c.Next() -} - -func (c *Cursor) PeekPrevious() bool { - defer c.Save()() - return c.Previous() -} - func (c *Cursor) Stmt() ast.Stmt { return c.statements[c.currentIdx] } diff --git a/wsl.go b/wsl.go index eb642e0..afc156f 100644 --- a/wsl.go +++ b/wsl.go @@ -21,8 +21,11 @@ type FixRange struct { } type Issue struct { - Message string - FixRanges []FixRange // TODO: Do we need multiple? + Message string + // We can report multiple fixes at the same position. This happens e.g. when + // we force error cuddling but the error assignment is already cuddled. + // See `checkError` for examples. + FixRanges []FixRange } type WSL struct { @@ -30,7 +33,7 @@ type WSL struct { Fset *token.FileSet TypeInfo *types.Info Comments ast.CommentMap - Issues map[token.Pos]Issue // TODO: When do we have multiple reports? + Issues map[token.Pos]Issue Config *Configuration } @@ -365,10 +368,8 @@ func (w *WSL) CheckGo(stmt *ast.GoStmt, cursor *Cursor) { stmt, stmt.Call, cursor, - // We can cuddle any amount `go` statements so only check cuddling if the - // previous one isn't a `go` call. - // We don't even have to check if it's actually cuddled or just the previous - // one because even if it's not but is a `go` statement it's valid. + // We can cuddle any amount `go` statements so only check cuddling if + // the previous one isn't a `go` call. func(n ast.Node) (int, bool) { _, ok := n.(*ast.GoStmt) return 1, !ok @@ -386,7 +387,9 @@ func (w *WSL) CheckDefer(stmt *ast.DeferStmt, cursor *Cursor) { _, previousIsDefer := n.(*ast.DeferStmt) _, previousIsIf := n.(*ast.IfStmt) - // We allow defer as a third node only if we if check between. E.g. + // We allow defer as a third node only if we have an if statement + // between, e.g. + // // f, err := os.Open(file) // if err != nil { // return err @@ -403,6 +406,7 @@ func (w *WSL) CheckDefer(stmt *ast.DeferStmt, cursor *Cursor) { } } + // Only check cuddling if previous statement isn't also a defer. return 1, !previousIsDefer }, CheckDefer, @@ -519,8 +523,7 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { } // There's only a return statement. - noStmts := cursor.Len() - if noStmts <= 1 { + if cursor.Len() <= 1 { return } From b28eebb5684edf8b9941b1950557776205e4f505 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 21 Apr 2025 20:12:56 +0200 Subject: [PATCH 058/112] Add support for send, update conifg --- config.go | 27 +++++++++++++------ testdata/src/default_config/send/send.go | 17 ++++++++++++ .../src/default_config/send/send.go.golden | 20 ++++++++++++++ .../src/with_config/disable_all/disable.go | 8 ++++++ wsl.go | 15 +++++++++-- 5 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 testdata/src/default_config/send/send.go create mode 100644 testdata/src/default_config/send/send.go.golden diff --git a/config.go b/config.go index 63c90f2..36a9683 100644 --- a/config.go +++ b/config.go @@ -31,6 +31,7 @@ const ( CheckRange CheckReturn CheckSelect + CheckSend CheckSwitch CheckTypeSwitch @@ -164,6 +165,7 @@ func AllChecks() CheckSet { c := DefaultChecks() c.Add(CheckAssignExclusive) c.Add(CheckErr) + c.Add(CheckSend) return c } @@ -184,10 +186,6 @@ func CheckFromString(s string) (CheckType, error) { switch strings.ToLower(s) { case "assign": return CheckAssign, nil - case "assign-exclusive": - return CheckAssignExclusive, nil - case "append": - return CheckAppend, nil case "break": return CheckBreak, nil case "continue": @@ -206,18 +204,31 @@ func CheckFromString(s string) (CheckType, error) { return CheckIf, nil case "incdec": return CheckIncDec, nil - case "leading-whitespace": - return CheckLeadingWhitespace, nil - case "trailing-whitespace": - return CheckTrailingWhitespace, nil + case "label": + return CheckLabel, nil case "range": return CheckRange, nil case "return": return CheckReturn, nil + case "select": + return CheckSelect, nil + case "send": + return CheckSend, nil case "switch": return CheckSwitch, nil case "type-switch": return CheckTypeSwitch, nil + + case "append": + return CheckAppend, nil + case "assign-exclusive": + return CheckAssignExclusive, nil + case "err": + return CheckErr, nil + case "leading-whitespace": + return CheckLeadingWhitespace, nil + case "trailing-whitespace": + return CheckTrailingWhitespace, nil default: return CheckInvalid, fmt.Errorf("invalid check '%s'", s) } diff --git a/testdata/src/default_config/send/send.go b/testdata/src/default_config/send/send.go new file mode 100644 index 0000000..ee6dd1c --- /dev/null +++ b/testdata/src/default_config/send/send.go @@ -0,0 +1,17 @@ +package testpkg + +func fn1(ch chan int) { + a := 1 + ch <- 1 // want "missing whitespace decreases readability" + + b := 2 + <-ch // want "missing whitespace decreases readability" +} + +func fn2(ch chan int) { + a := 1 + ch <- a + + b := 1 + b = <-ch +} diff --git a/testdata/src/default_config/send/send.go.golden b/testdata/src/default_config/send/send.go.golden new file mode 100644 index 0000000..404e7c8 --- /dev/null +++ b/testdata/src/default_config/send/send.go.golden @@ -0,0 +1,20 @@ +package testpkg + +func fn1(ch chan int) { + a := 1 + + ch <- 1 // want "missing whitespace decreases readability" + + b := 2 + + <-ch // want "missing whitespace decreases readability" +} + +func fn2(ch chan int) { + a := 1 + ch <- a + + b := 1 + b = <-ch +} + diff --git a/testdata/src/with_config/disable_all/disable.go b/testdata/src/with_config/disable_all/disable.go index 9287837..535b92a 100644 --- a/testdata/src/with_config/disable_all/disable.go +++ b/testdata/src/with_config/disable_all/disable.go @@ -133,6 +133,14 @@ func select(ctx context.Context, ch1 chan struct{}) { _ = x } +func send(ch chan int) { + a := 1 + ch <- 1 + + b := 2 + <-ch +} + func switch() { a := 1 b := 2 diff --git a/wsl.go b/wsl.go index afc156f..d66fe63 100644 --- a/wsl.go +++ b/wsl.go @@ -350,6 +350,18 @@ func (w *WSL) CheckSelect(stmt *ast.SelectStmt, cursor *Cursor) { w.MaybeCheckBlock(stmt, stmt.Body, cursor, CheckSelect) } +func (w *WSL) CheckSend(stmt *ast.SendStmt, cursor *Cursor) { + defer func() { + w.CheckExpr(stmt.Value, cursor) + }() + + if _, ok := w.Config.Checks[CheckSend]; !ok { + return + } + + w.CheckCuddling(stmt, cursor, 1) +} + func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { w.MaybeCheckExpr( stmt, @@ -678,8 +690,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { w.CheckSelect(s, cursor) // ch <- ... case *ast.SendStmt: - // TODO: Check cuddling? - w.CheckExpr(s.Value, cursor) + w.CheckSend(s, cursor) // LABEL: case *ast.LabeledStmt: w.CheckLabel(s, cursor) From 070c6b0584f2ae40f48f88b6c34cf602e881af7f Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 21 Apr 2025 21:21:22 +0200 Subject: [PATCH 059/112] Update README --- README.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++---- config.go | 2 +- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e57e835..8c20872 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,72 @@ wsl --disable-all --enable break,continue --fix ./... `wsl` is also integrated in [`golangci-lint`][golangci-lint] ```sh -golangci-lint run --no-config --disable-all --enable wsl --fix +golangci-lint run --no-config --enable-only wsl --fix ``` -## Issues and configuration +## Checks and configuration -TODO +Each check can be disabled or enabled individually to the point where no checks +can be run. The idea with this is to attract more users. + +### Checks + +This is an exhaustive list of all the checks that can be enabled or disabled. +The names are the same as the Go AST type name. + +#### Built-ins and keywords + +- **assign** - Any amount of assignments can be cuddled +- **break** +- **continue** +- **decl** - Any amount of declarations can be cuddled, preferable in a group +- **defer** +- **expr** - E.g. function calls +- **for** +- **go** +- **if** +- **inc-dec** - Increment or decrement, e.g. `i++`/`i--` +- **label** +- **range** +- **return** +- **select** +- **send** - Sending on channels, e.g. `ch <- val` +- **switch** +- **type-switch** + +#### Specific `wsl` cases + +- **assign-exclusive** - Only allow cuddling either new variables or reassigning + of existing ones. +- **append** - Only allow reassigning with `append` if the value being appended + exist on the line above. +- **err** - Error checking must follow immediately after the error variable is + assigned. +- **leading-whitespace** - Disallow leading empty lines in blocks. +- **trailing-whitespace** - Disallow trailing empty lines in blocks. + +### Configuration + +Other than enabling or disabling specific checks some checks can be configured +in more details. + +- **allow-first-in-block** - Allow cuddling a variable if it's used first in the + immediate following block, even if the statement with the block doesn't use + the variable. + + ```go + a := 1 + if someCond { + fmt.Println(a) + } + ``` + +- **allow-whole-block** - Same as above, but allows cuddling if the variable is + used _anywhere_ in the following (or nested) block. +- **case-max-lines** - If set to a non negative number, `case` blocks needs to + end with a whitespace if exceeding this number. +- **return-max-lines** - If a block contains less than this number of lines the + return statement does not need to be separated by a whitespace. ## See also @@ -44,5 +104,4 @@ TODO [gofumpt]: https://github.com/mvdan/gofumpt [golangci-lint]: https://golangci-lint.run [nlreturn]: https://github.com/ssgreg/nlreturn - [suggested-fixes-issue]: https://github.com/golangci/golangci-lint/issues/1779 [whitespace]: https://github.com/ultraware/whitespace diff --git a/config.go b/config.go index 36a9683..c7eda08 100644 --- a/config.go +++ b/config.go @@ -202,7 +202,7 @@ func CheckFromString(s string) (CheckType, error) { return CheckGo, nil case "if": return CheckIf, nil - case "incdec": + case "inc-dec": return CheckIncDec, nil case "label": return CheckLabel, nil From 48688ce8b2c6e4ed45b9d62dc309a1dee5b14367 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 2 May 2025 19:06:18 +0200 Subject: [PATCH 060/112] Configuration and flag changes --- analyzer.go | 59 ++++++++++++++++++++++++++++++++++++++--------------- config.go | 45 +++++++++++++++++++++++++++++----------- wsl.go | 11 ++++++---- 3 files changed, 82 insertions(+), 33 deletions(-) diff --git a/analyzer.go b/analyzer.go index 53337e4..b4b4854 100644 --- a/analyzer.go +++ b/analyzer.go @@ -5,6 +5,7 @@ import ( "go/ast" "go/token" "strings" + "sync" "golang.org/x/tools/go/analysis" ) @@ -24,31 +25,44 @@ func NewAnalyzer(config *Configuration) *analysis.Analyzer { // wslAnalyzer is a wrapper around the configuration which is used to be able to // set the configuration when creating the analyzer and later be able to update // flags and running method. -// -// TODO: This should be a public configuration you can pass to `NewAnalyzer` to -// enable `golang-lint` to pass `includeGenerated` but also pass a simpler -// enable/disable list of strings. type wslAnalyzer struct { - config *Configuration - includeGenerated bool - enableAll bool - disableAll bool - enable []string - disable []string + config *Configuration + + // When we use flags, we need to parse the ones used for checks into + // temporary variables so we can create the check set once the flag is being + // parsed by the analyzer and we run our analyzer. + enableAll bool + disableAll bool + enable []string + disable []string + + // To only validate and convert the parsed flags once we use a `sync.Once` + // to only create a check set once and store the set and potential error. We + // also store if we actually had a configuration to ensure we don't + // overwrite the checks if the analyzer was created with a proper wsl + // config. + cfgOnce sync.Once + didHaveConfig bool + checkSet CheckSet + checkSetErr error } func (wa *wslAnalyzer) flags() flag.FlagSet { - flags := flag.NewFlagSet("", flag.ExitOnError) + flags := flag.NewFlagSet("wsl", flag.ExitOnError) - // If we have a configuration set we're not running from the command line so - // we don't use any flags. if wa.config != nil { + wa.didHaveConfig = true return *flags } wa.config = NewConfig() - flags.BoolVar(&wa.includeGenerated, "include-generated", false, "Include generated files") + flags.BoolVar(&wa.config.IncludeGenerated, "include-generated", false, "Include generated files") + flags.BoolVar(&wa.config.AllowFirstInBlock, "allow-first-in-block", true, "Allow cuddling if variable is used in the first statement in the block") + flags.BoolVar(&wa.config.AllowWholeBlock, "allow-whole-block", false, "Allow cuddling if variable is used anywhere in the block") + flags.IntVar(&wa.config.CaseMaxLines, "case-max-lines", 0, "Max lines before requiring a newline at the end of case (0 = never)") + flags.IntVar(&wa.config.BranchMaxLines, "branch-max-lines", 2, "Max lines before requiring newline before branching, e.g. `return`, `break`, `continue` (0 = never)") + flags.BoolVar(&wa.enableAll, "enable-all", false, "Enable all checks") flags.BoolVar(&wa.disableAll, "disable-all", false, "Disable all checks") flags.Var(&multiStringValue{slicePtr: &wa.enable}, "enable", "Comma separated list of checks to enable") @@ -58,8 +72,19 @@ func (wa *wslAnalyzer) flags() flag.FlagSet { } func (wa *wslAnalyzer) run(pass *analysis.Pass) (any, error) { - if err := wa.config.Update(wa.enableAll, wa.disableAll, wa.enable, wa.disable); err != nil { - return nil, err + wa.cfgOnce.Do(func() { + // No need to update checks if config was passed when creating the + // analyzer. + if wa.didHaveConfig { + return + } + + // Parse the check params once if we set our config from flags. + wa.config.Checks, wa.checkSetErr = NewCheckSet(wa.enableAll, wa.disableAll, wa.enable, wa.disable) + }) + + if wa.checkSetErr != nil { + return nil, wa.checkSetErr } for _, file := range pass.Files { @@ -81,7 +106,7 @@ func (wa *wslAnalyzer) run(pass *analysis.Pass) (any, error) { // The file is skipped if the "unadjusted" file is a Go file, and it's a // generated file (ex: "_test.go" file). The other non-Go files are // skipped by the first 'if' with the adjusted position. - if !wa.includeGenerated && ast.IsGenerated(file) { + if !wa.config.IncludeGenerated && ast.IsGenerated(file) { continue } diff --git a/config.go b/config.go index c7eda08..b16e7a0 100644 --- a/config.go +++ b/config.go @@ -81,60 +81,81 @@ Configuration migration - IncludeGenerated | Done. */ type Configuration struct { + IncludeGenerated bool AllowFirstInBlock bool AllowWholeBlock bool CaseMaxLines int - ReturnMaxLines int + BranchMaxLines int Checks CheckSet } func NewConfig() *Configuration { return &Configuration{ + IncludeGenerated: false, AllowFirstInBlock: true, AllowWholeBlock: false, CaseMaxLines: 0, - ReturnMaxLines: 2, + BranchMaxLines: 2, Checks: DefaultChecks(), } } -func (c *Configuration) Update( +func NewWithChecks( enableAll bool, disableAll bool, enable []string, disable []string, -) error { +) (*Configuration, error) { + checks, err := NewCheckSet(enableAll, disableAll, enable, disable) + if err != nil { + return nil, fmt.Errorf("failed to create config: %w", err) + } + + cfg := NewConfig() + cfg.Checks = checks + + return cfg, nil +} + +func NewCheckSet( + enableAll bool, + disableAll bool, + enable []string, + disable []string, +) (CheckSet, error) { + cs := DefaultChecks() + if enableAll && disableAll { - return errors.New("can't use both `enable-all` and `disable-all`") + return nil, errors.New("can't use both `enable-all` and `disable-all`") } if enableAll { - c.Checks = AllChecks() + cs = AllChecks() } if disableAll { - c.Checks = NoChecks() + cs = NoChecks() } for _, s := range enable { check, err := CheckFromString(s) if err != nil { - return fmt.Errorf("invalid check '%s'", s) + return nil, fmt.Errorf("invalid check '%s'", s) } - c.Checks.Add(check) + cs.Add(check) } for _, s := range disable { check, err := CheckFromString(s) if err != nil { - return fmt.Errorf("invalid check '%s'", s) + return nil, fmt.Errorf("invalid check '%s'", s) } - c.Checks.Remove(check) + cs.Remove(check) } - return nil + return cs, nil } func DefaultChecks() CheckSet { diff --git a/wsl.go b/wsl.go index d66fe63..2e20481 100644 --- a/wsl.go +++ b/wsl.go @@ -438,9 +438,10 @@ func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { return } - // TODO: Should this be lines or statements? lastStmtInBlock := cursor.statements[len(cursor.statements)-1] - if stmt == lastStmtInBlock && len(cursor.statements) <= 2 { + firstStmts := cursor.Nth(0) + + if w.lineFor(lastStmtInBlock.End())-w.lineFor(firstStmts.Pos()) < w.Config.BranchMaxLines { return } @@ -511,7 +512,6 @@ func (w *WSL) checkCaseTrailingNewline(body []ast.Stmt, cursor *Cursor) { lastStmt := body[len(body)-1] totalLines := w.lineFor(lastStmt.End()) - w.lineFor(firstStmt.Pos()) + 1 - // Not exceeding max lines to require newline. if totalLines <= w.Config.CaseMaxLines { return } @@ -546,7 +546,7 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { // If the distance between the first statement and the return statement is // less than 3 LOC we're allowed to cuddle. firstStmts := cursor.Nth(0) - if w.lineFor(stmt.End())-w.lineFor(firstStmts.Pos()) < w.Config.ReturnMaxLines { + if w.lineFor(stmt.End())-w.lineFor(firstStmts.Pos()) < w.Config.BranchMaxLines { return } @@ -876,6 +876,9 @@ func (w *WSL) lineFor(pos token.Pos) int { func (w *WSL) implementsErr(node *ast.Ident) bool { typeInfo := w.TypeInfo.TypeOf(node) + if typeInfo == nil { + return false + } errorType, ok := types.Universe.Lookup("error").Type().Underlying().(*types.Interface) if !ok { From 0a9087aaefb4ef7b473aa853ffc7d93329313201 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 2 May 2025 19:11:58 +0200 Subject: [PATCH 061/112] Move send test to test with config --- testdata/src/{default_config => with_config}/send/send.go | 0 .../src/{default_config => with_config}/send/send.go.golden | 0 wsl_test.go | 6 ++++++ 3 files changed, 6 insertions(+) rename testdata/src/{default_config => with_config}/send/send.go (100%) rename testdata/src/{default_config => with_config}/send/send.go.golden (100%) diff --git a/testdata/src/default_config/send/send.go b/testdata/src/with_config/send/send.go similarity index 100% rename from testdata/src/default_config/send/send.go rename to testdata/src/with_config/send/send.go diff --git a/testdata/src/default_config/send/send.go.golden b/testdata/src/with_config/send/send.go.golden similarity index 100% rename from testdata/src/default_config/send/send.go.golden rename to testdata/src/with_config/send/send.go.golden diff --git a/wsl_test.go b/wsl_test.go index 16cdc56..5e86394 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -67,6 +67,12 @@ func TestWithConfig(t *testing.T) { config.Checks.Add(CheckAssignExclusive) }, }, + { + subdir: "send", + configFn: func(config *Configuration) { + config.Checks.Add(CheckSend) + }, + }, { subdir: "disable_all", configFn: func(config *Configuration) { From 32a00e1f1c38f6575038a2c7247b23022a749095 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 2 May 2025 19:39:09 +0200 Subject: [PATCH 062/112] Update README --- README.md | 82 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 8c20872..c48d54a 100644 --- a/README.md +++ b/README.md @@ -39,45 +39,62 @@ can be run. The idea with this is to attract more users. This is an exhaustive list of all the checks that can be enabled or disabled. The names are the same as the Go AST type name. +✅ = enabled by default, ❌ = disabled by default + #### Built-ins and keywords -- **assign** - Any amount of assignments can be cuddled -- **break** -- **continue** -- **decl** - Any amount of declarations can be cuddled, preferable in a group -- **defer** -- **expr** - E.g. function calls -- **for** -- **go** -- **if** -- **inc-dec** - Increment or decrement, e.g. `i++`/`i--` -- **label** -- **range** -- **return** -- **select** -- **send** - Sending on channels, e.g. `ch <- val` -- **switch** -- **type-switch** +- ✅ **assign** - Assignments should only be cuddled with other assignments, + declarations or increment/decrement. +- ✅ **break** - Break should only be cuddled if the block is less than `n` lines + (default 2, 0 = off) +- ✅ **continue** - Continue should only be cuddled if the block is less than `n` + lines (default 2, 0 = off) +- ✅ **decl** = Declarations should only be cuddled with one assignments, or + increment/decrement used on the line above. +- ✅ **defer** - Defer should only be cuddled with other `defer`, after error + checking or with variable used on the line above. +- ✅ **expr** - Expressions are e.g. function calls or index expressions, they + should only be cuddled with variables used on the line above. +- ✅ **for** - For loops should only be cuddled with a single variable used on the + line above. +- ✅ **go** - Go should only be cuddled with other `go` or with variables used on + the line above. +- ✅ **if** - If should only be cuddled with a single variable used on the line + above. +- ✅ **inc-dec** - Increment/decrement (`i++/i--`) has the same rules as `assign` +- ✅ **label** - Labels should never be cuddled. +- ✅ **range** - Range should only be cuddled with a single variable used on the + line above. +- ✅ **return** - Return should only be cuddled if the block is less than `n` lines + (default 2, 0 = off) +- ✅ **select** - Select should only be cuddled with a single variable used on the + line above. +- ❌ **send** - Send should only be cuddled with a single variable used on the line + above (default off) +- ✅ **switch** - Switch should only be cuddled with a single variable used on the + line above. +- ✅ **type-switch** - Type switch should only be cuddled with a single variable + used on the line above. #### Specific `wsl` cases -- **assign-exclusive** - Only allow cuddling either new variables or reassigning - of existing ones. -- **append** - Only allow reassigning with `append` if the value being appended - exist on the line above. -- **err** - Error checking must follow immediately after the error variable is - assigned. -- **leading-whitespace** - Disallow leading empty lines in blocks. -- **trailing-whitespace** - Disallow trailing empty lines in blocks. +- ❌ **assign-exclusive** - Only allow cuddling either new variables or reassigning + of existing ones +- ✅ **append** - Only allow reassigning with `append` if the value being appended + exist on the line above +- ❌ **err** - Error checking must follow immediately after the error variable is + assigned +- ✅ **leading-whitespace** - Disallow leading empty lines in blocks +- ✅ **trailing-whitespace** - Disallow trailing empty lines in blocks ### Configuration Other than enabling or disabling specific checks some checks can be configured in more details. -- **allow-first-in-block** - Allow cuddling a variable if it's used first in the +- ✅ **allow-first-in-block** - Allow cuddling a variable if it's used first in the immediate following block, even if the statement with the block doesn't use - the variable. + the variable ```go a := 1 @@ -86,12 +103,13 @@ in more details. } ``` -- **allow-whole-block** - Same as above, but allows cuddling if the variable is - used _anywhere_ in the following (or nested) block. +- ❌ **allow-whole-block** - Same as above, but allows cuddling if the variable is + used _anywhere_ in the following (or nested) block - **case-max-lines** - If set to a non negative number, `case` blocks needs to - end with a whitespace if exceeding this number. -- **return-max-lines** - If a block contains less than this number of lines the - return statement does not need to be separated by a whitespace. + end with a whitespace if exceeding this number (default 0, 0 = off) +- **branch-max-lines** - If a block contains less than this number of lines the + branch statement (e.g. `return`, `break`, `continue`) does not need to be + separated by a whitespace (default 2, 0 = off) ## See also From 5df9c8da9f8bb622daf888ecbfe2273a270fca00 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 2 May 2025 23:31:42 +0200 Subject: [PATCH 063/112] WIP: Rules --- README.md | 2 + RULES.md | 734 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 736 insertions(+) create mode 100644 RULES.md diff --git a/README.md b/README.md index c48d54a..fed1278 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ can be run. The idea with this is to attract more users. This is an exhaustive list of all the checks that can be enabled or disabled. The names are the same as the Go AST type name. +For more details and examples, see [RULES](RULES.md). + ✅ = enabled by default, ❌ = disabled by default #### Built-ins and keywords diff --git a/RULES.md b/RULES.md new file mode 100644 index 0000000..ad611a2 --- /dev/null +++ b/RULES.md @@ -0,0 +1,734 @@ +# Checks + +This document describes all the checks done by `wsl` with examples of what's not +allowed and what's allowed. + +## `assign` + +Assign is for assignments such as `foo := bar` or re-assignments such as `foo = +bar`. Assignments should only be cuddled with other assigments, declarations or +increment/decrement. + + + + + + + +
BadGood
+ +```go +if true { + fmt.Println("hello") +} +a := 1 +``` + + + +```go +if true { + fmt.Println("hello") +} + +a := 1 + +b := 2 +c := 3 +d := 4 +``` + +
+ +The assignment is cuddled directly with an `if` statement. + + + +The assignment is separated from the `if` statement with an empty line. + +
+ +## `break` + +> Configurable via `branch-max-lines` + + + + + + + +
BadGood
+ +```go +for { + a, err : = SomeFn() + if err != nil { + return err + } + + fmt.Println(a) + break +} +``` + + + +```go +for { + a, err : = SomeFn() + if err != nil { + return err + } + + fmt.Println(a) + + break +} + +for { + fmt.Println("hello") + break +} +``` + +
+ +The block with the `break` control flow spans over quite some lines so it's easy +to miss the cuddled control flow. + + + +In the bigger block the control flow is separated with an empty line. For +smaller blocks with only 2 lines it's ok to cuddled the `break`. + +
+ +## `continue` + +> Configurable via `branch-max-lines` + +See [`break`](#break), same rules apply but for the keyword `continue`. + +## `decl` + +Declarations can serve the purpose of pre-declaring variables or even assign +them for later use, e.g. when passing a reference. It can also be used to group +and align multiple values for readability. + +> **NOTE** The fixer can't do smart adjustments and currently only add +> whitespaces. + + + + + + + +
BadGood
+ +```go +var x string +fmt.Println("hello") +a := 1 +var y int +var z int +``` + + + +```go +var ( + x string + y int + z int +) + +fmt.Println("hello") + +a := 1 +``` + +
+ +Declarations mixed with both assignments and expressions. Consecutive +declarations are not grouped together. + + + +All declarations are grouped in a single declaration, aligning them for +increased readability. + +
+ +## `defer` + +Deferring execution should only be used directly in the context of what's being +deferred. + + + + + + + +
BadGood
+ +```go +val, closeFn := SomeFn() +fmt.Println(val) +defer closeFn() + +defer fn1() +a := 1 +defer fn3() + +f, err := os.Open("/path/to/f.txt") +if err != nil { + return err +} + +lines := ReadFile(f) +trimLines(lines) +defer f.Close() + +m.Lock() +defer m.Unlock() +``` + + + +```go +val, closeFn := SomeFn() +defer closeFn() + +defer fn1() +defer fn2() +defer fn3() + +f, err := os.Open("/path/to/f.txt") +if err != nil { + return err +} +defer f.Close() + +m.Lock() +defer m.Unlock() +``` + +
+ +Defer calls not related to the what's being deferred. Squeezing extra statements +between assignments and when the defer happens makes it easier to lose context +of what's actually being deferred. + + + +Examples of defer statements close to its context. Immediately after a function +variable is assigned, multiple defer in a row, immediately after error handling +and immediately after a mutex is locked. + +
+ +## `expr` + +Expressions can be multiple things and a big part of them are not handled by +`wsl`. However all function calls are expressions which can be verified. + + + + + + + +
BadGood
+ +```go +a := 1 +b := 2 +fmt.Println("not b") +``` + + + +```go +a := 1 +b := 2 + +fmt.Println("not b") + +a := 1 +fmt.Println(a) +``` + +
+ +The function call to `Println` isn't related to the variables on the line above. + + + +The call to `Println` uses the variable immediately above and is therefor in the +same context as the assignment. + +
+ +## `for` + +See [`if`](#if), same rules apply but for the keyword `for`. + +> Configurable via `allow-first-in-block` to allow cuddling if the variable is +> used _first_ in the block (enabled by default). +> +> Configurable via `allow-whole-block` to allow cuddling if the variable is used +> _anywhere_ in the following block (disabled by default). + + + + + + + +
BadGood
+ +```go +i := 0 +for j := 0; j < 3; j++ { + fmt.Println(j) +} + +x := 1 +for { + fmt.Println("hello") + break +} +``` + + + +```go +i := 0 +for j := 0; j < i; j++ { + fmt.Println(j) +} + +// Allowed with `allow-first-in-block` +x := 1 +for { + x++ + break +} +``` + +
+ +Variables above the `for` loop is not used in the `for` statement. + + + +The variable on the line above is used in the loop condition. + +
+ +## `go` + +See [`defer`](#defer), same rules apply but for the keyword `go`. + +## `if` + +> Configurable via `allow-first-in-block` to allow cuddling if the variable is +> used _first_ in the block (enabled by default). +> +> Configurable via `allow-whole-block` to allow cuddling if the variable is used +> _anywhere_ in the following block (disabled by default). + +`if` statements is one of several block statements (a statement with a block) +that can have some form of expression or condition. To make block context more +readable, only one variable is allowed immediately above the `if` statement and +the variable must be used in the condition (unless configured otherwise). + + + + + + + +
BadGood
+ +```go +x := 1 +if y > 1 { + fmt.Println("y > 1") +} + +a := 1 +b := 2 +if b > 1 { + fmt.Println("a > 1") +} + +a := 1 +b := 2 +if a > 1 { + fmt.Println("a > b") +} + +a := 1 +b := 2 +if notEvenAOrB() { + fmt.Println("not a or b") +} + +a := 1 +x, err := SomeFn() +if err != nil { + return err +} +``` + + + +```go +x := 1 + +if y > 1 { + fmt.Println("y > 1") +} + +a := 1 + +b := 2 +if b > 1 { + fmt.Println("a > 1") +} + +b := 2 + +a := 1 +if a > 1 { + fmt.Println("a > b") +} + +a := 1 +b := 2 + +if notEvenAOrB() { + fmt.Println("not a or b") +} + +a := 1 + +x, err := SomeFn() +if err != nil { + return err +} + +// Allowed with `allow-first-in-block` +x := 1 +if xUsedFirstInBlock() { + x = 2 +} + +// Allowed with `allow-whole-block` +x := 1 +if xUsedFirstInBlock() { + fmt.Println("will use x later") + + x = 2 + + if orEvenNestedWouldWork() { + x = 3 + } +} +``` + +
+ +Multiple `if` statements where the variable on the line above is either not used +in the `if` statement or there are more than one cuddled assignment above. + + + +Only when a single variable that is used in the `if` statement below is declared +or assigned do we cuddled it. This also shows examples for the configuration. + +
+ +## `inc-dec` + +See [`assign`](#assign), same rules apply but for increment (`++`) and decrement +(`--`) + +## `label` + +Labels should never be cuddled. Labels in itself is often a symptom of big scope +and split context and because of that should always have an empty line above. + + + + + + + +
BadGood
+ +```go +L1: + if true { + _ = 1 + } +L2: + if true { + _ = 1 + } +``` + + + +```go +L1: + if true { + _ = 1 + } + +L2: + if true { + _ = 1 + } +``` + +
+ +The label `L2` is directly cuddled with the statement above. + + + +The label `L2` has an empty line above. + +
+ +## `range` + +See [`for`](#for), same rules apply but for the keyword `range`. + +> Configurable via `allow-first-in-block` to allow cuddling if the variable is +> used _first_ in the block (enabled by default). +> +> Configurable via `allow-whole-block` to allow cuddling if the variable is used +> _anywhere_ in the following block (disabled by default). + + + + + + + +
BadGood
+ +```go +someRange := []int{1, 2, 3} +for _, i := range anotherRange { + fmt.Println(i) +} + +x := 1 +for { + fmt.Println("hello") + break +} +``` + + + +```go +someRange := []int{1, 2, 3} + +for _, i := range anotherRange { + fmt.Println(i) +} + +someRange := []int{1, 2, 3} +for _, i := range someRange { + fmt.Println(i) +} + +notARange := 1 +for i := range returnsRange(notARange) { + fmt.Println(i) +} +``` + +
+ +Slices that is not related to the `range` is cuddled with the `range` statement. + + + +Only variables used in the range are cuddled. + +
+ +## `return` + +See [`break`](#break), same rules apply but for the keyword `return`. + +## `select` + +## `send` + +## `switch` + +## `type-switch` + +## `assign-exclusive` + +## `append` + + + + + + + +
BadGood
+ +```go +s := []string{} + +a := 1 +s = append(s, 2) +b := 3 +s = append(s, a) +``` + + + +```go +s := []string{} + +a := 1 +s = append(s, a) + +b := 3 + +s = append(s, 2) +``` + +
+ +Assignments not related to the slice appending is mixed and matched, making +context unclear. + + + +Assignments used in the appending are cuddled since they share context, other +assignments are separated with a newline. + +
+ +## `err` + + + + + + + +
BadGood
+ +```go +_, err := SomeFn() + +if err != nil { + return fmt.Errorf("failed to fn: %w", err) +} +``` + + + +```go +_, err := SomeFn() +if err != nil { + return fmt.Errorf("failed to fn: %w", err) +} +``` + +
+ +The error checking is separated from the context where it was assigned with an +unnecessary newline. + + + +The error checking happens immediately after the assignment. + +
+ +## `leading-whitespace` + + + + + + + +
BadGood
+ +```go +if true { + + fmt.Println("hello") +} +``` + + + +```go +if true { + fmt.Println("hello") +} +``` + +
+ +The block starts with an unnecessary trailing whitespace. + + + +The block does not have any unnecessary whitespaces. + +
+ +## `trailing-whitespace` + + + + + + + +
BadGood
+ +```go +if true { + fmt.Println("hello") + +} +``` + + + +```go +if true { + fmt.Println("hello") +} +``` + +
+ +The block ends with an unnecessary trailing whitespace. + + + +The block does not have any unnecessary whitespaces. + +
From da1204a36a26d66c368b352270cce90007c68a14 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 3 May 2025 16:44:38 +0200 Subject: [PATCH 064/112] More docs and small fixes --- README.md | 153 ++++++++----- RULES.md | 214 +++++++++++++++--- analyzer.go | 3 +- config.go | 2 +- testdata/src/default_config/decl/decl.go | 13 +- .../src/default_config/decl/decl.go.golden | 14 +- .../src/with_config/disable_all/disable.go | 2 + wsl.go | 21 +- 8 files changed, 308 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index fed1278..b83a080 100644 --- a/README.md +++ b/README.md @@ -8,27 +8,6 @@ lines to separate grouping of different types to increase readability. There are also a few places where it encourages you to _remove_ whitespaces which is at the start and the end of blocks. -## Usage - -> **Note**: This linter provides a fixer that can fix most issues with the -> `--fix` flag. - -`wsl` uses the [analysis] package meaning it will operate on package level with -the default analysis flags and way of working. - -```sh -wsl --help -wsl [flags] - -wsl --disable-all --enable break,continue --fix ./... -``` - -`wsl` is also integrated in [`golangci-lint`][golangci-lint] - -```sh -golangci-lint run --no-config --enable-only wsl --fix -``` - ## Checks and configuration Each check can be disabled or enabled individually to the point where no checks @@ -36,8 +15,9 @@ can be run. The idea with this is to attract more users. ### Checks -This is an exhaustive list of all the checks that can be enabled or disabled. -The names are the same as the Go AST type name. +This is an exhaustive list of all the checks that can be enabled or disabled and +their default value. The names are the same as the Go +[AST](https://pkg.go.dev/go/ast) type name for built-ins. For more details and examples, see [RULES](RULES.md). @@ -46,37 +26,36 @@ For more details and examples, see [RULES](RULES.md). #### Built-ins and keywords - ✅ **assign** - Assignments should only be cuddled with other assignments, - declarations or increment/decrement. -- ✅ **break** - Break should only be cuddled if the block is less than `n` lines - (default 2, 0 = off) -- ✅ **continue** - Continue should only be cuddled if the block is less than `n` - lines (default 2, 0 = off) -- ✅ **decl** = Declarations should only be cuddled with one assignments, or - increment/decrement used on the line above. + declarations or increment/decrement +- ✅ **break** - Break should only be cuddled if the block is less than `n` + lines where `n` is the value of [`branch-max-statements`](#configuration) +- ✅ **continue** - Continue should only be cuddled if the block is less than + `n` lines where `n` is the value of [`branch-max-statements`](#configuration) +- ✅ **decl** - Declarations should never be cuddled - ✅ **defer** - Defer should only be cuddled with other `defer`, after error - checking or with variable used on the line above. + checking or with variable used on the line above - ✅ **expr** - Expressions are e.g. function calls or index expressions, they - should only be cuddled with variables used on the line above. + should only be cuddled with variables used on the line above - ✅ **for** - For loops should only be cuddled with a single variable used on the - line above. + line above - ✅ **go** - Go should only be cuddled with other `go` or with variables used on - the line above. + the line above - ✅ **if** - If should only be cuddled with a single variable used on the line - above. -- ✅ **inc-dec** - Increment/decrement (`i++/i--`) has the same rules as `assign` -- ✅ **label** - Labels should never be cuddled. + above +- ✅ **inc-dec** - Increment/decrement (`++/--`) has the same rules as `assign` +- ✅ **label** - Labels should never be cuddled - ✅ **range** - Range should only be cuddled with a single variable used on the - line above. -- ✅ **return** - Return should only be cuddled if the block is less than `n` lines - (default 2, 0 = off) + line above +- ✅ **return** - Return should only be cuddled if the block is less than `n` + lines where `n` is the value of [`branch-max-statements`](#configuration) - ✅ **select** - Select should only be cuddled with a single variable used on the - line above. + line above - ❌ **send** - Send should only be cuddled with a single variable used on the line - above (default off) + above - ✅ **switch** - Switch should only be cuddled with a single variable used on the - line above. + line above - ✅ **type-switch** - Type switch should only be cuddled with a single variable - used on the line above. + used on the line above #### Specific `wsl` cases @@ -97,21 +76,87 @@ in more details. - ✅ **allow-first-in-block** - Allow cuddling a variable if it's used first in the immediate following block, even if the statement with the block doesn't use the variable - - ```go - a := 1 - if someCond { - fmt.Println(a) - } - ``` - - ❌ **allow-whole-block** - Same as above, but allows cuddling if the variable is used _anywhere_ in the following (or nested) block -- **case-max-lines** - If set to a non negative number, `case` blocks needs to - end with a whitespace if exceeding this number (default 0, 0 = off) - **branch-max-lines** - If a block contains less than this number of lines the branch statement (e.g. `return`, `break`, `continue`) does not need to be separated by a whitespace (default 2, 0 = off) +- **case-max-lines** - If set to a non negative number, `case` blocks needs to + end with a whitespace if exceeding this number (default 0, 0 = off) +- ❌ **include-generated** - Include generated files when checking + +## Installation + +```sh +# Latest release +go install github.com/bombsimon/wsl/v5/cmd/wsl@latest + +# Main branch +go install github.com/bombsimon/wsl/v5/cmd/wsl@master +``` + +## Usage + +> **Note**: This linter provides a fixer that can fix most issues with the +> `--fix` flag. + +`wsl` uses the [analysis] package meaning it will operate on package level with +the default analysis flags and way of working. + +```sh +wsl --help +wsl [flags] + +wsl --disable-all --enable break,continue --fix ./... +``` + +`wsl` is also integrated in [`golangci-lint`][golangci-lint] + +```sh +golangci-lint run --no-config --enable-only wsl --fix +``` + +This is an exhaustive, default, configuration for `wsl` in `golangci-lint`. + +```yaml +linters: + default: none + enable: + - wsl + + settings: + wsl: + allow-first-in-block: true + allow-whole-block: false + branch-max-lines: 2 + case-max-lines: 0 + enable-all: false + disable-all: false + enable: + - assign + - break + - continue + - decl + - defer + - expr + - for + - go + - if + - inc-dec + - label + - range + - return + - select + - switch + - type-switch + - append + - leading-whitespace + - trailing-whitespace + disable: + - assign-exclusive + - err + - send +``` ## See also diff --git a/RULES.md b/RULES.md index ad611a2..07b215c 100644 --- a/RULES.md +++ b/RULES.md @@ -5,9 +5,8 @@ allowed and what's allowed. ## `assign` -Assign is for assignments such as `foo := bar` or re-assignments such as `foo = -bar`. Assignments should only be cuddled with other assigments, declarations or -increment/decrement. +Assign (`foo := bar`) or re-assignments (`foo = bar`) should only be cuddled +with other assignments, declarations or increment/decrement. @@ -19,6 +18,11 @@ if true { fmt.Println("hello") } a := 1 + +defer func() { + fmt.Println("hello") +}() +a := 1 ```
BadGood
@@ -30,20 +34,28 @@ if true { a := 1 +defer func() { + fmt.Println("hello") +}() + +a := 1 + +a := 1 b := 2 c := 3 -d := 4 ```
-The assignment is cuddled directly with an `if` statement. +Assignments cuddled with statements that are not assignments, e.g. `if` or +`defer`. -The assignment is separated from the `if` statement with an empty line. +The assignment is separated from non-assignment statement with an empty line. +This also shows multiple assignments cuddled together which is allowed.
@@ -112,9 +124,10 @@ See [`break`](#break), same rules apply but for the keyword `continue`. ## `decl` -Declarations can serve the purpose of pre-declaring variables or even assign -them for later use, e.g. when passing a reference. It can also be used to group -and align multiple values for readability. +Declarations should never be cuddled. When grouping multiple declarations +together they should be declared in the same group with parenthesis into a +single statement. The benefit of this is that it also aligns the declaration or +assignment increasing readability. > **NOTE** The fixer can't do smart adjustments and currently only add > whitespaces. @@ -125,25 +138,39 @@ and align multiple values for readability. ```go -var x string -fmt.Println("hello") +var a string +var b int + +const a = 1 +const b = 2 + a := 1 -var y int -var z int +var b string + +fmt.Println("hello") +var a string ``` ```go var ( - x string - y int - z int + a string + b int ) -fmt.Println("hello") +const ( + a = 1 + b = 2 +) a := 1 + +var b string + +fmt.Println("hello") + +var a string ``` @@ -156,7 +183,7 @@ declarations are not grouped together. All declarations are grouped in a single declaration, aligning them for -increased readability. +increased readability. Declarations are separated from other statements. @@ -164,7 +191,7 @@ increased readability. ## `defer` Deferring execution should only be used directly in the context of what's being -deferred. +deferred and there should only be one statement above. @@ -173,6 +200,7 @@ deferred. ```go val, closeFn := SomeFn() +val2 := fmt.Sprintf("v-%s", val) fmt.Println(val) defer closeFn() @@ -314,6 +342,16 @@ for { x++ break } + +// Allowed with `allow-whole-block` +x := 1 +for { + fmt.Println("hello") + + if shouldIncrement() { + x++ + } +} ``` @@ -324,7 +362,8 @@ Variables above the `for` loop is not used in the `for` statement.
BadGood
-The variable on the line above is used in the loop condition. +The variable on the line above is used in the loop condition. Additionally there +are examples for `allow-first-in-block` and `allow-whole-block`.
@@ -341,7 +380,7 @@ See [`defer`](#defer), same rules apply but for the keyword `go`. > Configurable via `allow-whole-block` to allow cuddling if the variable is used > _anywhere_ in the following block (disabled by default). -`if` statements is one of several block statements (a statement with a block) +`if` statements are one of several block statements (a statement with a block) that can have some form of expression or condition. To make block context more readable, only one variable is allowed immediately above the `if` statement and the variable must be used in the condition (unless configured otherwise). @@ -430,8 +469,6 @@ x := 1 if xUsedFirstInBlock() { fmt.Println("will use x later") - x = 2 - if orEvenNestedWouldWork() { x = 3 } @@ -528,10 +565,16 @@ for _, i := range anotherRange { } x := 1 -for { +for i := range make([]int, 3) { fmt.Println("hello") break } + +s1 := []int{1, 2, 3} +s2 := []int{3, 2, 1} +for _, v := range s2 { + fmt.Println(v) +} ``` @@ -552,17 +595,26 @@ notARange := 1 for i := range returnsRange(notARange) { fmt.Println(i) } + +s1 := []int{1, 2, 3} + +s2 := []int{3, 2, 1} +for _, v := range s2 { + fmt.Println(v) +} ``` -Slices that is not related to the `range` is cuddled with the `range` statement. +Slices that are not related to the `range` is cuddled with the `range` +statement. Multiple statements are cuddled above the range statement. -Only variables used in the range are cuddled. +Only variables used in the `range` are cuddled and at most one statement above the +`range` statement. @@ -571,18 +623,128 @@ Only variables used in the range are cuddled. See [`break`](#break), same rules apply but for the keyword `return`. +> Configurable via `branch-max-lines` + ## `select` +See [`for`](#for), same rules apply but for the keyword `range`. + +> Configurable via `allow-first-in-block` to allow cuddling if the variable is +> used _first_ in the block (enabled by default). +> +> Configurable via `allow-whole-block` to allow cuddling if the variable is used +> _anywhere_ in the following block (disabled by default). + ## `send` +Send statements should only be cuddled with a single variable that is used on +the line above. + + + + + + + +
BadGood
+ +```go +a := 1 +ch <- 1 + +b := 2 +<-ch +``` + + + +```go +a := 1 +ch <- a + +b := 1 + +<-ch +``` + +
+ +Send statement cuddled with assignments that doesn't exist on the line above or +with multiple assignments. + + + +Send statements are only cuddled with single variables on the line above. + +
+ ## `switch` +See [`for`](#for), same rules apply but for the keyword `range`. + +> Configurable via `allow-first-in-block` to allow cuddling if the variable is +> used _first_ in the block (enabled by default). +> +> Configurable via `allow-whole-block` to allow cuddling if the variable is used +> _anywhere_ in the following block (disabled by default). + ## `type-switch` +See [`for`](#for), same rules apply but for the keyword `range`. + +> Configurable via `allow-first-in-block` to allow cuddling if the variable is +> used _first_ in the block (enabled by default). +> +> Configurable via `allow-whole-block` to allow cuddling if the variable is used +> _anywhere_ in the following block (disabled by default). + ## `assign-exclusive` +Assign exclusive does not allow mixing new assignments (`:=`) with +re-assignments (`=`). + + + + + + + +
BadGood
+ +```go +a := 1 +b = 2 +c := 3 +d = 4 +``` + + + +```go +a := 1 +c := 3 + +b = 2 +d = 4 +``` + +
+ +Alternating new assignments and re-assignments. + + + +Separating assignments and re-assignments. + +
+ ## `append` +Append enables strict `append` checking where assignments that are +re-assignments with `append` (e.g. `x = append(x, y)`) is only allowed to be +cuddled with other assignments if the `append` uses the variable on the line +above. + diff --git a/analyzer.go b/analyzer.go index b4b4854..0a3e30b 100644 --- a/analyzer.go +++ b/analyzer.go @@ -43,7 +43,6 @@ type wslAnalyzer struct { // config. cfgOnce sync.Once didHaveConfig bool - checkSet CheckSet checkSetErr error } @@ -60,8 +59,8 @@ func (wa *wslAnalyzer) flags() flag.FlagSet { flags.BoolVar(&wa.config.IncludeGenerated, "include-generated", false, "Include generated files") flags.BoolVar(&wa.config.AllowFirstInBlock, "allow-first-in-block", true, "Allow cuddling if variable is used in the first statement in the block") flags.BoolVar(&wa.config.AllowWholeBlock, "allow-whole-block", false, "Allow cuddling if variable is used anywhere in the block") - flags.IntVar(&wa.config.CaseMaxLines, "case-max-lines", 0, "Max lines before requiring a newline at the end of case (0 = never)") flags.IntVar(&wa.config.BranchMaxLines, "branch-max-lines", 2, "Max lines before requiring newline before branching, e.g. `return`, `break`, `continue` (0 = never)") + flags.IntVar(&wa.config.CaseMaxLines, "case-max-lines", 0, "Max lines before requiring a newline at the end of case (0 = never)") flags.BoolVar(&wa.enableAll, "enable-all", false, "Enable all checks") flags.BoolVar(&wa.disableAll, "disable-all", false, "Disable all checks") diff --git a/config.go b/config.go index b16e7a0..af79535 100644 --- a/config.go +++ b/config.go @@ -84,8 +84,8 @@ type Configuration struct { IncludeGenerated bool AllowFirstInBlock bool AllowWholeBlock bool - CaseMaxLines int BranchMaxLines int + CaseMaxLines int Checks CheckSet } diff --git a/testdata/src/default_config/decl/decl.go b/testdata/src/default_config/decl/decl.go index 2554f68..01b6d45 100644 --- a/testdata/src/default_config/decl/decl.go +++ b/testdata/src/default_config/decl/decl.go @@ -10,17 +10,14 @@ func fn1() { e := 5 var f = 6 // want "missing whitespace decreases readability" g := 7 // want "missing whitespace decreases readability" - - _ = a - _ = b - _ = c - _ = d - _ = e - _ = f - _ = g } func fn2() { var a = 1 var b = a // want "missing whitespace decreases readability" } + +func fn3() { + a := 1 + var b = a // want "missing whitespace decreases readability" +} diff --git a/testdata/src/default_config/decl/decl.go.golden b/testdata/src/default_config/decl/decl.go.golden index 67d0b8b..ca7ef7d 100644 --- a/testdata/src/default_config/decl/decl.go.golden +++ b/testdata/src/default_config/decl/decl.go.golden @@ -14,14 +14,6 @@ func fn1() { var f = 6 // want "missing whitespace decreases readability" g := 7 // want "missing whitespace decreases readability" - - _ = a - _ = b - _ = c - _ = d - _ = e - _ = f - _ = g } func fn2() { @@ -29,3 +21,9 @@ func fn2() { var b = a // want "missing whitespace decreases readability" } + +func fn3() { + a := 1 + + var b = a // want "missing whitespace decreases readability" +} diff --git a/testdata/src/with_config/disable_all/disable.go b/testdata/src/with_config/disable_all/disable.go index 535b92a..9e94872 100644 --- a/testdata/src/with_config/disable_all/disable.go +++ b/testdata/src/with_config/disable_all/disable.go @@ -27,9 +27,11 @@ func branch() { func decl() { var a = 1 var b = 1 + var c = b _ = a _ = b + _ = c } func defer() { diff --git a/wsl.go b/wsl.go index 2e20481..130ccc9 100644 --- a/wsl.go +++ b/wsl.go @@ -61,15 +61,11 @@ func (w *WSL) CheckCuddlingBlock(stmt ast.Node, blockList []ast.Stmt, cursor *Cu firstBlockStmt = blockList[0] } - w.checkCuddlingWithDecl(stmt, firstBlockStmt, cursor, maxAllowedStatements, true) + w.checkCuddlingWithDecl(stmt, firstBlockStmt, cursor, maxAllowedStatements) } func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { - w.checkCuddlingWithDecl(stmt, nil, cursor, maxAllowedStatements, true) -} - -func (w *WSL) CheckCuddlingNoDecl(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { - w.checkCuddlingWithDecl(stmt, nil, cursor, maxAllowedStatements, false) + w.checkCuddlingWithDecl(stmt, nil, cursor, maxAllowedStatements) } func (w *WSL) checkCuddlingWithDecl( @@ -77,7 +73,6 @@ func (w *WSL) checkCuddlingWithDecl( firstBlockStmt ast.Node, cursor *Cursor, maxAllowedStatements int, - declIsValid bool, ) { defer cursor.Save()() @@ -107,13 +102,6 @@ func (w *WSL) checkCuddlingWithDecl( _, prevIsIncDec := previousNode.(*ast.IncDecStmt) _, currIsDefer := stmt.(*ast.DeferStmt) - // Most of the time we allow cuddling with declarations (var) if it's just - // one statement but not always so this can be disabled, e.g. for - // delclarations themselves. - if !declIsValid { - prevIsDecl = false - } - // We're cuddled but not with an assign, declare or defer statement which is // never allowed. if !prevIsAssign && !prevIsDecl && !currIsDefer && !prevIsIncDec { @@ -453,11 +441,14 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { return } + // TODO: Decl might be a block that needs analyzing, e.g. + // var x = func() {} + if w.numberOfStatementsAbove(cursor) == 0 { return } - w.CheckCuddlingNoDecl(stmt, cursor, 1) + w.addError(stmt.End(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) } func (w *WSL) CheckBlock(block *ast.BlockStmt) *Cursor { From 5569812460e71d6c1afed3f7d58de1c2505421b8 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 3 May 2025 22:14:06 +0200 Subject: [PATCH 065/112] Add check type to diagnostic, make branch a single check --- README.md | 18 +++--- RULES.md | 12 ++-- config.go | 45 +++++++++++--- cursor.go | 5 ++ testdata/src/default_config/assign/assign.go | 4 +- .../default_config/assign/assign.go.golden | 4 +- testdata/src/default_config/branch/branch.go | 42 ++++++++++++- .../default_config/branch/branch.go.golden | 44 +++++++++++++- wsl.go | 59 ++++++++++++------- 9 files changed, 178 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index b83a080..99d6cda 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,9 @@ For more details and examples, see [RULES](RULES.md). - ✅ **assign** - Assignments should only be cuddled with other assignments, declarations or increment/decrement -- ✅ **break** - Break should only be cuddled if the block is less than `n` - lines where `n` is the value of [`branch-max-statements`](#configuration) -- ✅ **continue** - Continue should only be cuddled if the block is less than - `n` lines where `n` is the value of [`branch-max-statements`](#configuration) +- ✅ **branch** - Branch statement (`break`, `continue`, `fallthrough`, `goto`) + should only be cuddled if the block is less than `n` lines where `n` is the + value of [`branch-max-statements`](#configuration) - ✅ **decl** - Declarations should never be cuddled - ✅ **defer** - Defer should only be cuddled with other `defer`, after error checking or with variable used on the line above @@ -134,8 +133,7 @@ linters: disable-all: false enable: - assign - - break - - continue + - branch - decl - defer - expr @@ -152,10 +150,10 @@ linters: - append - leading-whitespace - trailing-whitespace - disable: - - assign-exclusive - - err - - send + disable: + - assign-exclusive + - err + - send ``` ## See also diff --git a/RULES.md b/RULES.md index 07b215c..1782bf2 100644 --- a/RULES.md +++ b/RULES.md @@ -60,10 +60,14 @@ This also shows multiple assignments cuddled together which is allowed.
BadGood
-## `break` +## `branch` > Configurable via `branch-max-lines` +Branch statement (`break`, `continue`, `fallthrough`, `goto`) should only be +cuddled if the block is less than `n` lines where `n` is the value of +`branch-max-statements`. + @@ -116,12 +120,6 @@ smaller blocks with only 2 lines it's ok to cuddled the `break`.
BadGood
-## `continue` - -> Configurable via `branch-max-lines` - -See [`break`](#break), same rules apply but for the keyword `continue`. - ## `decl` Declarations should never be cuddled. When grouping multiple declarations diff --git a/config.go b/config.go index af79535..e7989d1 100644 --- a/config.go +++ b/config.go @@ -18,8 +18,7 @@ type CheckType int const ( CheckInvalid CheckType = iota CheckAssign - CheckBreak - CheckContinue + CheckBranch CheckDecl CheckDefer CheckExpr @@ -60,8 +59,41 @@ const ( CheckErr CheckLeadingWhitespace CheckTrailingWhitespace + + // CheckTypes only used for reporting + CheckCaseTrailingNewline ) +func (c CheckType) String() string { + return [...]string{ + "invalid", + "assign", + "branch", + "decl", + "defer", + "expr", + "for", + "go", + "if", + "inc-dec", + "label", + "range", + "return", + "select", + "send", + "switch", + "type-switch", + // + "assign-exclusive", + "append", + "err", + "leading-whitespace", + "trailing-whitespace", + // + "case-trailing-newline", + }[c] +} + /* Configuration migration @@ -162,8 +194,7 @@ func DefaultChecks() CheckSet { return CheckSet{ CheckAssign: {}, CheckAppend: {}, - CheckBreak: {}, - CheckContinue: {}, + CheckBranch: {}, CheckDecl: {}, CheckDefer: {}, CheckExpr: {}, @@ -207,10 +238,8 @@ func CheckFromString(s string) (CheckType, error) { switch strings.ToLower(s) { case "assign": return CheckAssign, nil - case "break": - return CheckBreak, nil - case "continue": - return CheckContinue, nil + case "branch": + return CheckBranch, nil case "decl": return CheckDecl, nil case "defer": diff --git a/cursor.go b/cursor.go index 71bfc65..a4ad021 100644 --- a/cursor.go +++ b/cursor.go @@ -10,6 +10,7 @@ import ( type Cursor struct { currentIdx int statements []ast.Stmt + checkType CheckType } // NewCursor creates a new cursor with a given list of statements. @@ -20,6 +21,10 @@ func NewCursor(statements []ast.Stmt) *Cursor { } } +func (c *Cursor) SetChecker(ct CheckType) { + c.checkType = ct +} + func (c *Cursor) Next() bool { if c.currentIdx >= len(c.statements)-1 { return false diff --git a/testdata/src/default_config/assign/assign.go b/testdata/src/default_config/assign/assign.go index 6c41b0b..bf9c4d6 100644 --- a/testdata/src/default_config/assign/assign.go +++ b/testdata/src/default_config/assign/assign.go @@ -29,7 +29,7 @@ func strictAppend() { x := "c" s = append(s, x) y := "e" - s = append(s, "d") // want "missing whitespace decreases readability" + s = append(s, "d") // want `missing whitespace decreases readability \(assign\)` s = append(s, y) } @@ -62,5 +62,5 @@ func assignAfterBlock() { if x > 0 { return } - x = 2 // want "missing whitespace decreases readability" + x = 2 // want `missing whitespace decreases readability \(assign\)` } diff --git a/testdata/src/default_config/assign/assign.go.golden b/testdata/src/default_config/assign/assign.go.golden index 7d8c566..752228f 100644 --- a/testdata/src/default_config/assign/assign.go.golden +++ b/testdata/src/default_config/assign/assign.go.golden @@ -30,7 +30,7 @@ func strictAppend() { s = append(s, x) y := "e" - s = append(s, "d") // want "missing whitespace decreases readability" + s = append(s, "d") // want `missing whitespace decreases readability \(assign\)` s = append(s, y) } @@ -64,5 +64,5 @@ func assignAfterBlock() { return } - x = 2 // want "missing whitespace decreases readability" + x = 2 // want `missing whitespace decreases readability \(assign\)` } diff --git a/testdata/src/default_config/branch/branch.go b/testdata/src/default_config/branch/branch.go index 9608930..570efa9 100644 --- a/testdata/src/default_config/branch/branch.go +++ b/testdata/src/default_config/branch/branch.go @@ -10,6 +10,11 @@ func fn1() { continue } + if true { + fmt.Println("") + break + } + if true { fmt.Println("") continue @@ -17,6 +22,19 @@ func fn1() { if true { fmt.Println("") + fallthrough + } + + if true { + fmt.Println("") + goto START + } + + if false { + fmt.Println("") + fmt.Println("") + fmt.Println("") + break } @@ -33,13 +51,28 @@ func fn1() { fmt.Println("") fmt.Println("") - break + fallthrough + } + + if false { + fmt.Println("") + fmt.Println("") + fmt.Println("") + + goto START } } } func fn2() { for range []int{} { + if true { + fmt.Println("") + fmt.Println("") + fmt.Println("") + break // want "missing whitespace decreases readability" + } + if true { fmt.Println("") fmt.Println("") @@ -51,9 +84,14 @@ func fn2() { fmt.Println("") fmt.Println("") fmt.Println("") + fallthrough // want "missing whitespace decreases readability" + } + + if true { fmt.Println("") fmt.Println("") - break // want "missing whitespace decreases readability" + fmt.Println("") + goto START // want "missing whitespace decreases readability" } } } diff --git a/testdata/src/default_config/branch/branch.go.golden b/testdata/src/default_config/branch/branch.go.golden index 21beba6..845d223 100644 --- a/testdata/src/default_config/branch/branch.go.golden +++ b/testdata/src/default_config/branch/branch.go.golden @@ -10,6 +10,11 @@ func fn1() { continue } + if true { + fmt.Println("") + break + } + if true { fmt.Println("") continue @@ -17,6 +22,19 @@ func fn1() { if true { fmt.Println("") + fallthrough + } + + if true { + fmt.Println("") + goto START + } + + if false { + fmt.Println("") + fmt.Println("") + fmt.Println("") + break } @@ -33,13 +51,29 @@ func fn1() { fmt.Println("") fmt.Println("") - break + fallthrough + } + + if false { + fmt.Println("") + fmt.Println("") + fmt.Println("") + + goto START } } } func fn2() { for range []int{} { + if true { + fmt.Println("") + fmt.Println("") + fmt.Println("") + + break // want "missing whitespace decreases readability" + } + if true { fmt.Println("") fmt.Println("") @@ -52,10 +86,16 @@ func fn2() { fmt.Println("") fmt.Println("") fmt.Println("") + + fallthrough // want "missing whitespace decreases readability" + } + + if true { + fmt.Println("") fmt.Println("") fmt.Println("") - break // want "missing whitespace decreases readability" + goto START // want "missing whitespace decreases readability" } } } diff --git a/wsl.go b/wsl.go index 130ccc9..b3309a6 100644 --- a/wsl.go +++ b/wsl.go @@ -105,7 +105,7 @@ func (w *WSL) checkCuddlingWithDecl( // We're cuddled but not with an assign, declare or defer statement which is // never allowed. if !prevIsAssign && !prevIsDecl && !currIsDefer && !prevIsIncDec { - w.addError(cursor.Stmt().Pos(), cursor.Stmt().Pos(), cursor.Stmt().Pos(), MessageAddWhitespace) + w.addError(cursor.Stmt().Pos(), cursor.Stmt().Pos(), cursor.Stmt().Pos(), MessageAddWhitespace, cursor.checkType) return } @@ -117,7 +117,7 @@ func (w *WSL) checkCuddlingWithDecl( if len(anyIntersects) > 0 { // We have matches, but too many statements above. if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { - w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) + w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace, cursor.checkType) } return @@ -132,7 +132,7 @@ func (w *WSL) checkCuddlingWithDecl( if len(anyIntersects) > 0 { // We have matches, but too many statements above. if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { - w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) + w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace, cursor.checkType) } return @@ -143,7 +143,7 @@ func (w *WSL) checkCuddlingWithDecl( // variables used in this statement. intersects := identIntersection(currentIdents, previousIdents) if len(intersects) == 0 { - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) return } @@ -154,7 +154,7 @@ func (w *WSL) checkCuddlingWithDecl( // disabled (-1) check that the previous one intersects with the current // one. if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { - w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) + w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace, cursor.checkType) } } } @@ -208,7 +208,7 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { } if w.numberOfStatementsAbove(cursor) > 0 && !prevIsValidType { - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) } } @@ -227,6 +227,8 @@ func (w *WSL) checkError( return } + cursor.SetChecker(CheckErr) + if stmtsAbove > 0 || len(previousIdents) == 0 { return } @@ -237,7 +239,7 @@ func (w *WSL) checkError( return } - w.addError(ifStmt.Pos(), previousNode.End(), ifStmt.Pos(), MessageRemoveWhitespace) + w.addError(ifStmt.Pos(), previousNode.End(), ifStmt.Pos(), MessageRemoveWhitespace, cursor.checkType) // If we add the error at the same position but with a different fix // range, only the fix range will be updated. @@ -256,7 +258,7 @@ func (w *WSL) checkError( cursor.Previous() if w.numberOfStatementsAbove(cursor) > 0 { - w.addError(ifStmt.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace) + w.addError(ifStmt.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace, cursor.checkType) } } @@ -269,6 +271,7 @@ func (w *WSL) MaybeCheckBlock( w.CheckBlock(blockStmt) if _, ok := w.Config.Checks[check]; ok { + cursor.SetChecker(check) w.CheckCuddlingBlock(node, blockStmt.List, cursor, 1) } } @@ -283,6 +286,7 @@ func (w *WSL) MaybeCheckExpr( w.CheckExpr(expr, cursor) if _, ok := w.Config.Checks[check]; ok { + cursor.SetChecker(check) previousNode := cursor.PreviousNode() if n, ok := predicate(previousNode); ok { @@ -314,6 +318,7 @@ func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor, isElse bool) { } if _, ok := w.Config.Checks[CheckIf]; !isElse && ok { + cursor.SetChecker(CheckIf) w.CheckCuddlingBlock(stmt, stmt.Body.List, cursor, 1) } } @@ -347,6 +352,8 @@ func (w *WSL) CheckSend(stmt *ast.SendStmt, cursor *Cursor) { return } + cursor.SetChecker(CheckSend) + w.CheckCuddling(stmt, cursor, 1) } @@ -414,13 +421,11 @@ func (w *WSL) CheckDefer(stmt *ast.DeferStmt, cursor *Cursor) { } func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { - if _, ok := w.Config.Checks[CheckBreak]; !ok && stmt.Tok == token.BREAK { + if _, ok := w.Config.Checks[CheckBranch]; !ok { return } - if _, ok := w.Config.Checks[CheckContinue]; !ok && stmt.Tok == token.CONTINUE { - return - } + cursor.SetChecker(CheckBranch) if w.numberOfStatementsAbove(cursor) == 0 { return @@ -433,7 +438,7 @@ func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { return } - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) } func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { @@ -441,6 +446,8 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { return } + cursor.SetChecker(CheckDecl) + // TODO: Decl might be a block that needs analyzing, e.g. // var x = func() {} @@ -448,7 +455,7 @@ func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { return } - w.addError(stmt.End(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) + w.addError(stmt.End(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) } func (w *WSL) CheckBlock(block *ast.BlockStmt) *Cursor { @@ -513,7 +520,7 @@ func (w *WSL) checkCaseTrailingNewline(body []ast.Stmt, cursor *Cursor) { return } - w.addError(lastStmt.End(), nextCase.Pos(), nextCase.Pos(), MessageAddWhitespace) + w.addError(lastStmt.End(), nextCase.Pos(), nextCase.Pos(), MessageAddWhitespace, CheckCaseTrailingNewline) } func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { @@ -525,6 +532,8 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { return } + cursor.SetChecker(CheckReturn) + // There's only a return statement. if cursor.Len() <= 1 { return @@ -541,7 +550,7 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { return } - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) } func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { @@ -555,6 +564,8 @@ func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { return } + cursor.SetChecker(CheckAssign) + w.CheckCuddlingWithoutIntersection(stmt, cursor) w.strictAppendCheck(stmt, cursor) } @@ -566,6 +577,8 @@ func (w *WSL) CheckIncDec(stmt *ast.IncDecStmt, cursor *Cursor) { return } + cursor.SetChecker(CheckIncDec) + w.CheckCuddlingWithoutIntersection(stmt, cursor) } @@ -574,11 +587,13 @@ func (w *WSL) CheckLabel(stmt *ast.LabeledStmt, cursor *Cursor) { return } + cursor.SetChecker(CheckLabel) + if w.numberOfStatementsAbove(cursor) == 0 { return } - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) } func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { @@ -611,7 +626,7 @@ func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { } if !hasIntersection(appendNode, previousNode) { - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace) + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) } } @@ -812,7 +827,7 @@ func (w *WSL) CheckLeadingNewline(startPos token.Pos, body []ast.Stmt, comments firstStmtLine := w.Fset.PositionFor(firstStmt, false).Line if openingPosLine != firstStmtLine-1 { - w.addError(openingPos, openingPos, firstStmt, MessageRemoveWhitespace) + w.addError(openingPos, openingPos, firstStmt, MessageRemoveWhitespace, CheckLeadingWhitespace) } } @@ -857,7 +872,7 @@ func (w *WSL) CheckTrailingNewline(body *ast.BlockStmt) { lastStmtLine := w.Fset.PositionFor(lastStmtOrComment, false).Line if closingPosLine != lastStmtLine+1 { - w.addError(closingPos, lastStmtOrComment, closingPos, MessageRemoveWhitespace) + w.addError(closingPos, lastStmtOrComment, closingPos, MessageRemoveWhitespace, CheckTrailingWhitespace) } } @@ -879,11 +894,11 @@ func (w *WSL) implementsErr(node *ast.Ident) bool { return types.Implements(typeInfo, errorType) } -func (w *WSL) addError(report, start, end token.Pos, message string) { +func (w *WSL) addError(report, start, end token.Pos, message string, ct CheckType) { issue, ok := w.Issues[report] if !ok { issue = Issue{ - Message: message, + Message: fmt.Sprintf("%s (%s)", message, ct), FixRanges: []FixRange{}, } } From e44fe8063b073bf7ff8e1304f3cb85be4bdfc938 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 3 May 2025 22:21:15 +0200 Subject: [PATCH 066/112] Add fix for label statement (actually check statement) --- testdata/src/default_config/label/label.go | 13 +++++++++++++ testdata/src/default_config/label/label.go.golden | 15 ++++++++++++++- wsl.go | 3 +++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/testdata/src/default_config/label/label.go b/testdata/src/default_config/label/label.go index 896450c..731b3db 100644 --- a/testdata/src/default_config/label/label.go +++ b/testdata/src/default_config/label/label.go @@ -1,5 +1,7 @@ package testpkg +import "fmt" + func fn1() { for i := range make([]int, 2) { if i == 1 { @@ -35,3 +37,14 @@ L2: // want "missing whitespace decreases readability" L3: // want "missing whitespace decreases readability" _ = 1 } + +func fn() { +LABEL: + if true { + fmt.Println("") + fmt.Println("") + fmt.Println("") + fmt.Println("") + break // want "missing whitespace decreases readability" + } +} diff --git a/testdata/src/default_config/label/label.go.golden b/testdata/src/default_config/label/label.go.golden index 0efac98..7caed63 100644 --- a/testdata/src/default_config/label/label.go.golden +++ b/testdata/src/default_config/label/label.go.golden @@ -1,5 +1,7 @@ package testpkg +import "fmt" + func fn1() { for i := range make([]int, 2) { if i == 1 { @@ -32,9 +34,20 @@ L2: // want "missing whitespace decreases readability" _ = 1 } - _ = 1 L3: // want "missing whitespace decreases readability" _ = 1 } + +func fn() { +LABEL: + if true { + fmt.Println("") + fmt.Println("") + fmt.Println("") + fmt.Println("") + + break // want "missing whitespace decreases readability" + } +} diff --git a/wsl.go b/wsl.go index b3309a6..5bc1697 100644 --- a/wsl.go +++ b/wsl.go @@ -583,6 +583,8 @@ func (w *WSL) CheckIncDec(stmt *ast.IncDecStmt, cursor *Cursor) { } func (w *WSL) CheckLabel(stmt *ast.LabeledStmt, cursor *Cursor) { + w.CheckStmt(stmt.Stmt, cursor) + if _, ok := w.Config.Checks[CheckLabel]; !ok { return } @@ -700,6 +702,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { // LABEL: case *ast.LabeledStmt: w.CheckLabel(s, cursor) + case *ast.EmptyStmt: default: fmt.Printf("Not implemented stmt: %T\n", s) } From fb0d412427f3c884da8379380a930736b33834d2 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 3 May 2025 23:23:14 +0200 Subject: [PATCH 067/112] Update tests with failed check --- testdata/src/default_config/branch/branch.go | 8 ++-- .../default_config/branch/branch.go.golden | 8 ++-- testdata/src/default_config/decl/decl.go | 12 +++--- .../src/default_config/decl/decl.go.golden | 12 +++--- testdata/src/default_config/defer/defer.go | 8 ++-- .../src/default_config/defer/defer.go.golden | 8 ++-- testdata/src/default_config/expr/expr.go | 6 +-- .../src/default_config/expr/expr.go.golden | 6 +-- testdata/src/default_config/for/for.go | 6 +-- testdata/src/default_config/for/for.go.golden | 6 +-- testdata/src/default_config/go/go.go | 10 ++--- testdata/src/default_config/go/go.go.golden | 10 ++--- testdata/src/default_config/if/if.go | 6 +-- testdata/src/default_config/if/if.go.golden | 6 +-- testdata/src/default_config/label/label.go | 6 +-- .../src/default_config/label/label.go.golden | 6 +-- testdata/src/default_config/range/range.go | 6 +-- .../src/default_config/range/range.go.golden | 6 +-- testdata/src/default_config/return/return.go | 10 ++--- .../default_config/return/return.go.golden | 10 ++--- testdata/src/default_config/select/select.go | 2 +- .../default_config/select/select.go.golden | 2 +- testdata/src/default_config/switch/switch.go | 6 +-- .../default_config/switch/switch.go.golden | 6 +-- .../default_config/type_switch/type_switch.go | 4 +- .../type_switch/type_switch.go.golden | 4 +- .../whitespace/leading_whitespace.go | 39 +++++++++---------- .../whitespace/leading_whitespace.go.golden | 38 +++++++++--------- .../whitespace/trailing_whitespace.go | 26 ++++++------- .../whitespace/trailing_whitespace.go.golden | 26 ++++++------- .../case_trailing_whitespace/case.go | 4 +- .../case_trailing_whitespace/case.go.golden | 4 +- .../with_config/exclusive_short_decl/code.go | 4 +- .../exclusive_short_decl/code.go.golden | 4 +- .../with_config/first_in_block_n1/block.go | 2 +- .../first_in_block_n1/block.go.golden | 2 +- .../with_config/if_errcheck/if_errcheck.go | 6 +-- .../if_errcheck/if_errcheck.go.golden | 6 +-- .../no_check_decl/no_check_decl.go.golden | 23 ----------- testdata/src/with_config/send/send.go | 4 +- testdata/src/with_config/send/send.go.golden | 4 +- testdata/src/with_config/whole_block/block.go | 12 +++--- .../with_config/whole_block/block.go.golden | 12 +++--- 43 files changed, 186 insertions(+), 210 deletions(-) delete mode 100644 testdata/src/with_config/no_check_decl/no_check_decl.go.golden diff --git a/testdata/src/default_config/branch/branch.go b/testdata/src/default_config/branch/branch.go index 570efa9..f7de78b 100644 --- a/testdata/src/default_config/branch/branch.go +++ b/testdata/src/default_config/branch/branch.go @@ -70,28 +70,28 @@ func fn2() { fmt.Println("") fmt.Println("") fmt.Println("") - break // want "missing whitespace decreases readability" + break // want `missing whitespace decreases readability \(branch\)` } if true { fmt.Println("") fmt.Println("") fmt.Println("") - continue // want "missing whitespace decreases readability" + continue // want `missing whitespace decreases readability \(branch\)` } if true { fmt.Println("") fmt.Println("") fmt.Println("") - fallthrough // want "missing whitespace decreases readability" + fallthrough // want `missing whitespace decreases readability \(branch\)` } if true { fmt.Println("") fmt.Println("") fmt.Println("") - goto START // want "missing whitespace decreases readability" + goto START // want `missing whitespace decreases readability \(branch\)` } } } diff --git a/testdata/src/default_config/branch/branch.go.golden b/testdata/src/default_config/branch/branch.go.golden index 845d223..7824b12 100644 --- a/testdata/src/default_config/branch/branch.go.golden +++ b/testdata/src/default_config/branch/branch.go.golden @@ -71,7 +71,7 @@ func fn2() { fmt.Println("") fmt.Println("") - break // want "missing whitespace decreases readability" + break // want `missing whitespace decreases readability \(branch\)` } if true { @@ -79,7 +79,7 @@ func fn2() { fmt.Println("") fmt.Println("") - continue // want "missing whitespace decreases readability" + continue // want `missing whitespace decreases readability \(branch\)` } if true { @@ -87,7 +87,7 @@ func fn2() { fmt.Println("") fmt.Println("") - fallthrough // want "missing whitespace decreases readability" + fallthrough // want `missing whitespace decreases readability \(branch\)` } if true { @@ -95,7 +95,7 @@ func fn2() { fmt.Println("") fmt.Println("") - goto START // want "missing whitespace decreases readability" + goto START // want `missing whitespace decreases readability \(branch\)` } } } diff --git a/testdata/src/default_config/decl/decl.go b/testdata/src/default_config/decl/decl.go index 01b6d45..1ee3403 100644 --- a/testdata/src/default_config/decl/decl.go +++ b/testdata/src/default_config/decl/decl.go @@ -2,22 +2,22 @@ package testpkg func fn1() { var a = 1 - var b = 2 // want "missing whitespace decreases readability" + var b = 2 // want `missing whitespace decreases readability \(decl\)` const c = 3 - const d = 4 // want "missing whitespace decreases readability" + const d = 4 // want `missing whitespace decreases readability \(decl\)` e := 5 - var f = 6 // want "missing whitespace decreases readability" - g := 7 // want "missing whitespace decreases readability" + var f = 6 // want `missing whitespace decreases readability \(decl\)` + g := 7 // want `missing whitespace decreases readability \(assign\)` } func fn2() { var a = 1 - var b = a // want "missing whitespace decreases readability" + var b = a // want `missing whitespace decreases readability \(decl\)` } func fn3() { a := 1 - var b = a // want "missing whitespace decreases readability" + var b = a // want `missing whitespace decreases readability \(decl\)` } diff --git a/testdata/src/default_config/decl/decl.go.golden b/testdata/src/default_config/decl/decl.go.golden index ca7ef7d..e1e1bef 100644 --- a/testdata/src/default_config/decl/decl.go.golden +++ b/testdata/src/default_config/decl/decl.go.golden @@ -3,27 +3,27 @@ package testpkg func fn1() { var a = 1 - var b = 2 // want "missing whitespace decreases readability" + var b = 2 // want `missing whitespace decreases readability \(decl\)` const c = 3 - const d = 4 // want "missing whitespace decreases readability" + const d = 4 // want `missing whitespace decreases readability \(decl\)` e := 5 - var f = 6 // want "missing whitespace decreases readability" + var f = 6 // want `missing whitespace decreases readability \(decl\)` - g := 7 // want "missing whitespace decreases readability" + g := 7 // want `missing whitespace decreases readability \(assign\)` } func fn2() { var a = 1 - var b = a // want "missing whitespace decreases readability" + var b = a // want `missing whitespace decreases readability \(decl\)` } func fn3() { a := 1 - var b = a // want "missing whitespace decreases readability" + var b = a // want `missing whitespace decreases readability \(decl\)` } diff --git a/testdata/src/default_config/defer/defer.go b/testdata/src/default_config/defer/defer.go index 8f3521f..3839b35 100644 --- a/testdata/src/default_config/defer/defer.go +++ b/testdata/src/default_config/defer/defer.go @@ -31,12 +31,12 @@ func fn() { a = Fn() defer a.Close() - b = Fn() // want "missing whitespace decreases readability" + b = Fn() // want `missing whitespace decreases readability \(assign\)` defer b.Close() a = Fn() b = Fn() - defer a.Close() // want "missing whitespace decreases readability" + defer a.Close() // want `missing whitespace decreases readability \(defer\)` defer b.Close() m := sync.Mutex{} @@ -45,7 +45,7 @@ func fn() { defer m.Unlock() c := true - defer func(b bool) { // want "missing whitespace decreases readability" + defer func(b bool) { // want `missing whitespace decreases readability \(defer\)` fmt.Printf("%v", b) }() @@ -54,7 +54,7 @@ func fn() { func fn2() { a := 1 - b := Fn() // want "missing whitespace decreases readability" + b := Fn() // want `missing whitespace decreases readability \(defer\)` defer b.Close() _ = a diff --git a/testdata/src/default_config/defer/defer.go.golden b/testdata/src/default_config/defer/defer.go.golden index 64ae67a..74c2eb5 100644 --- a/testdata/src/default_config/defer/defer.go.golden +++ b/testdata/src/default_config/defer/defer.go.golden @@ -32,13 +32,13 @@ func fn() { a = Fn() defer a.Close() - b = Fn() // want "missing whitespace decreases readability" + b = Fn() // want `missing whitespace decreases readability \(assign\)` defer b.Close() a = Fn() b = Fn() - defer a.Close() // want "missing whitespace decreases readability" + defer a.Close() // want `missing whitespace decreases readability \(defer\)` defer b.Close() m := sync.Mutex{} @@ -48,7 +48,7 @@ func fn() { c := true - defer func(b bool) { // want "missing whitespace decreases readability" + defer func(b bool) { // want `missing whitespace decreases readability \(defer\)` fmt.Printf("%v", b) }() @@ -58,7 +58,7 @@ func fn() { func fn2() { a := 1 - b := Fn() // want "missing whitespace decreases readability" + b := Fn() // want `missing whitespace decreases readability \(defer\)` defer b.Close() _ = a diff --git a/testdata/src/default_config/expr/expr.go b/testdata/src/default_config/expr/expr.go index 1a840ee..b1119c5 100644 --- a/testdata/src/default_config/expr/expr.go +++ b/testdata/src/default_config/expr/expr.go @@ -17,8 +17,8 @@ func fn() { func fn2() { a := 1 b := 2 - fmt.Println("") // want "missing whitespace decreases readability" - c := 3 // want "missing whitespace decreases readability" + fmt.Println("") // want `missing whitespace decreases readability \(expr\)` + c := 3 // want `missing whitespace decreases readability \(assign\)` d := 4 _ = a @@ -31,7 +31,7 @@ func fn3() { a := 1 b := 2 fmt.Println(b) - c := 3 // want "missing whitespace decreases readability" + c := 3 // want `missing whitespace decreases readability \(assign\)` d := 4 _ = a diff --git a/testdata/src/default_config/expr/expr.go.golden b/testdata/src/default_config/expr/expr.go.golden index ce27599..36473ea 100644 --- a/testdata/src/default_config/expr/expr.go.golden +++ b/testdata/src/default_config/expr/expr.go.golden @@ -18,9 +18,9 @@ func fn2() { a := 1 b := 2 - fmt.Println("") // want "missing whitespace decreases readability" + fmt.Println("") // want `missing whitespace decreases readability \(expr\)` - c := 3 // want "missing whitespace decreases readability" + c := 3 // want `missing whitespace decreases readability \(assign\)` d := 4 _ = a @@ -34,7 +34,7 @@ func fn3() { b := 2 fmt.Println(b) - c := 3 // want "missing whitespace decreases readability" + c := 3 // want `missing whitespace decreases readability \(assign\)` d := 4 _ = a diff --git a/testdata/src/default_config/for/for.go b/testdata/src/default_config/for/for.go index 90ba133..3488ef9 100644 --- a/testdata/src/default_config/for/for.go +++ b/testdata/src/default_config/for/for.go @@ -2,7 +2,7 @@ package testpkg func fn1() { a := 1 - b := 2 // want "missing whitespace decreases readability" + b := 2 // want `missing whitespace decreases readability \(for\)` for i := 0; i < b; i++ { panic(1) } @@ -14,7 +14,7 @@ func fn1() { func fn2() { b := 2 a := 1 - for i := 0; i < b; i++ { // want "missing whitespace decreases readability" + for i := 0; i < b; i++ { // want `missing whitespace decreases readability \(for\)` panic(1) } @@ -26,7 +26,7 @@ func fn3() { for i := 0; i < 1; i++ { panic("") } - for i := 0; i < 1; i++ { // want "missing whitespace decreases readability" + for i := 0; i < 1; i++ { // want `missing whitespace decreases readability \(for\)` panic("") } } diff --git a/testdata/src/default_config/for/for.go.golden b/testdata/src/default_config/for/for.go.golden index 48a5b0b..e14729e 100644 --- a/testdata/src/default_config/for/for.go.golden +++ b/testdata/src/default_config/for/for.go.golden @@ -3,7 +3,7 @@ package testpkg func fn1() { a := 1 - b := 2 // want "missing whitespace decreases readability" + b := 2 // want `missing whitespace decreases readability \(for\)` for i := 0; i < b; i++ { panic(1) } @@ -16,7 +16,7 @@ func fn2() { b := 2 a := 1 - for i := 0; i < b; i++ { // want "missing whitespace decreases readability" + for i := 0; i < b; i++ { // want `missing whitespace decreases readability \(for\)` panic(1) } @@ -29,7 +29,7 @@ func fn3() { panic("") } - for i := 0; i < 1; i++ { // want "missing whitespace decreases readability" + for i := 0; i < 1; i++ { // want `missing whitespace decreases readability \(for\)` panic("") } } diff --git a/testdata/src/default_config/go/go.go b/testdata/src/default_config/go/go.go index db77ae7..58e9257 100644 --- a/testdata/src/default_config/go/go.go +++ b/testdata/src/default_config/go/go.go @@ -13,7 +13,7 @@ func Go() { go fooFunc() barFunc := func() {} - go fooFunc() // want "missing whitespace decreases readability" + go fooFunc() // want `missing whitespace decreases readability \(go\)` _ = barFunc @@ -22,7 +22,7 @@ func Go() { }() cuddled := true - go func() { // want "missing whitespace decreases readability" + go func() { // want `missing whitespace decreases readability \(go\)` fmt.Println("hey") }() @@ -32,7 +32,7 @@ func Go() { go Fn(argToGo) notArgToGo := 1 - go Fn(argToGo) // want "missing whitespace decreases readability" + go Fn(argToGo) // want `missing whitespace decreases readability \(go\)` _ = notArgToGo @@ -45,13 +45,13 @@ func Go() { go t3() multiCuddle1 := NewT() - multiCuddle2 := NewT() // want "missing whitespace decreases readability" + multiCuddle2 := NewT() // want `missing whitespace decreases readability \(go\)` go multiCuddle2() // TODO: Breaking change, this used to be on the first `go` stmt - now it's // on the line that should have a blank line above. t4 := NewT() - t5 := NewT() // want "missing whitespace decreases readability" + t5 := NewT() // want `missing whitespace decreases readability \(go\)` go t5() go t4() diff --git a/testdata/src/default_config/go/go.go.golden b/testdata/src/default_config/go/go.go.golden index 78b0c70..c4dd006 100644 --- a/testdata/src/default_config/go/go.go.golden +++ b/testdata/src/default_config/go/go.go.golden @@ -15,7 +15,7 @@ func Go() { barFunc := func() {} - go fooFunc() // want "missing whitespace decreases readability" + go fooFunc() // want `missing whitespace decreases readability \(go\)` _ = barFunc @@ -25,7 +25,7 @@ func Go() { cuddled := true - go func() { // want "missing whitespace decreases readability" + go func() { // want `missing whitespace decreases readability \(go\)` fmt.Println("hey") }() @@ -36,7 +36,7 @@ func Go() { notArgToGo := 1 - go Fn(argToGo) // want "missing whitespace decreases readability" + go Fn(argToGo) // want `missing whitespace decreases readability \(go\)` _ = notArgToGo @@ -50,14 +50,14 @@ func Go() { multiCuddle1 := NewT() - multiCuddle2 := NewT() // want "missing whitespace decreases readability" + multiCuddle2 := NewT() // want `missing whitespace decreases readability \(go\)` go multiCuddle2() // TODO: Breaking change, this used to be on the first `go` stmt - now it's // on the line that should have a blank line above. t4 := NewT() - t5 := NewT() // want "missing whitespace decreases readability" + t5 := NewT() // want `missing whitespace decreases readability \(go\)` go t5() go t4() diff --git a/testdata/src/default_config/if/if.go b/testdata/src/default_config/if/if.go index 556b6a1..cfe1139 100644 --- a/testdata/src/default_config/if/if.go +++ b/testdata/src/default_config/if/if.go @@ -4,7 +4,7 @@ import "errors" func fn1() { a := 1 - b := 2 // want "missing whitespace decreases readability" + b := 2 // want `missing whitespace decreases readability \(if\)` if b == 2 { panic(1) } @@ -16,7 +16,7 @@ func fn1() { func fn2() { b := 2 a := 1 - if b == 2 { // want "missing whitespace decreases readability" + if b == 2 { // want `missing whitespace decreases readability \(if\)` panic(1) } @@ -36,7 +36,7 @@ func fn4() { if a := 1; a != 2 { panic(a) } - if a := 2; a != 2 { // want "missing whitespace decreases readability" + if a := 2; a != 2 { // want `missing whitespace decreases readability \(if\)` panic(a) } } diff --git a/testdata/src/default_config/if/if.go.golden b/testdata/src/default_config/if/if.go.golden index 9c0fee1..b03e023 100644 --- a/testdata/src/default_config/if/if.go.golden +++ b/testdata/src/default_config/if/if.go.golden @@ -5,7 +5,7 @@ import "errors" func fn1() { a := 1 - b := 2 // want "missing whitespace decreases readability" + b := 2 // want `missing whitespace decreases readability \(if\)` if b == 2 { panic(1) } @@ -18,7 +18,7 @@ func fn2() { b := 2 a := 1 - if b == 2 { // want "missing whitespace decreases readability" + if b == 2 { // want `missing whitespace decreases readability \(if\)` panic(1) } @@ -39,7 +39,7 @@ func fn4() { panic(a) } - if a := 2; a != 2 { // want "missing whitespace decreases readability" + if a := 2; a != 2 { // want `missing whitespace decreases readability \(if\)` panic(a) } } diff --git a/testdata/src/default_config/label/label.go b/testdata/src/default_config/label/label.go index 731b3db..2358049 100644 --- a/testdata/src/default_config/label/label.go +++ b/testdata/src/default_config/label/label.go @@ -28,13 +28,13 @@ L1: if true { _ = 1 } -L2: // want "missing whitespace decreases readability" +L2: // want `missing whitespace decreases readability \(if\)` if true { _ = 1 } _ = 1 -L3: // want "missing whitespace decreases readability" +L3: // want `missing whitespace decreases readability \(label\)` _ = 1 } @@ -45,6 +45,6 @@ LABEL: fmt.Println("") fmt.Println("") fmt.Println("") - break // want "missing whitespace decreases readability" + break // want `missing whitespace decreases readability \(branch\)` } } diff --git a/testdata/src/default_config/label/label.go.golden b/testdata/src/default_config/label/label.go.golden index 7caed63..abbaad1 100644 --- a/testdata/src/default_config/label/label.go.golden +++ b/testdata/src/default_config/label/label.go.golden @@ -29,14 +29,14 @@ L1: _ = 1 } -L2: // want "missing whitespace decreases readability" +L2: // want `missing whitespace decreases readability \(if\)` if true { _ = 1 } _ = 1 -L3: // want "missing whitespace decreases readability" +L3: // want `missing whitespace decreases readability \(label\)` _ = 1 } @@ -48,6 +48,6 @@ LABEL: fmt.Println("") fmt.Println("") - break // want "missing whitespace decreases readability" + break // want `missing whitespace decreases readability \(branch\)` } } diff --git a/testdata/src/default_config/range/range.go b/testdata/src/default_config/range/range.go index 3c3c5d1..ee13f4f 100644 --- a/testdata/src/default_config/range/range.go +++ b/testdata/src/default_config/range/range.go @@ -2,7 +2,7 @@ package testpkg func fn1() { a := []int{} - b := []int{} // want "missing whitespace decreases readability" + b := []int{} // want `missing whitespace decreases readability \(range\)` for range b { panic(1) } @@ -14,7 +14,7 @@ func fn1() { func fn2() { b := []int{} a := []int{} - for range b { // want "missing whitespace decreases readability" + for range b { // want `missing whitespace decreases readability \(range\)` panic(1) } @@ -26,7 +26,7 @@ func fn3() { for range make([]int, 0) { panic("") } - for range make([]int, 0) { // want "missing whitespace decreases readability" + for range make([]int, 0) { // want `missing whitespace decreases readability \(range\)` panic("") } } diff --git a/testdata/src/default_config/range/range.go.golden b/testdata/src/default_config/range/range.go.golden index be028a0..8b2e3bf 100644 --- a/testdata/src/default_config/range/range.go.golden +++ b/testdata/src/default_config/range/range.go.golden @@ -3,7 +3,7 @@ package testpkg func fn1() { a := []int{} - b := []int{} // want "missing whitespace decreases readability" + b := []int{} // want `missing whitespace decreases readability \(range\)` for range b { panic(1) } @@ -16,7 +16,7 @@ func fn2() { b := []int{} a := []int{} - for range b { // want "missing whitespace decreases readability" + for range b { // want `missing whitespace decreases readability \(range\)` panic(1) } @@ -29,7 +29,7 @@ func fn3() { panic("") } - for range make([]int, 0) { // want "missing whitespace decreases readability" + for range make([]int, 0) { // want `missing whitespace decreases readability \(range\)` panic("") } } diff --git a/testdata/src/default_config/return/return.go b/testdata/src/default_config/return/return.go index 9fac01f..f8d7740 100644 --- a/testdata/src/default_config/return/return.go +++ b/testdata/src/default_config/return/return.go @@ -12,29 +12,29 @@ func fn2() int { func fn3() int { _ = 1 _ = 2 - return 1 // want "missing whitespace decreases readability" + return 1 // want `missing whitespace decreases readability \(return\)` } func fn4() int { if true { _ = 1 } - return 1 // want "missing whitespace decreases readability" + return 1 // want `missing whitespace decreases readability \(return\)` } func fn5() int { if true { _ = 1 _ = 2 - return 1 // want "missing whitespace decreases readability" + return 1 // want `missing whitespace decreases readability \(return\)` } - return 1 // want "missing whitespace decreases readability" + return 1 // want `missing whitespace decreases readability \(return\)` } func fn6() func() { _ = 1 _ = 2 - return func() { // want "missing whitespace decreases readability" + return func() { // want `missing whitespace decreases readability \(return\)` _ = 1 } } diff --git a/testdata/src/default_config/return/return.go.golden b/testdata/src/default_config/return/return.go.golden index e125c91..c5968b6 100644 --- a/testdata/src/default_config/return/return.go.golden +++ b/testdata/src/default_config/return/return.go.golden @@ -13,7 +13,7 @@ func fn3() int { _ = 1 _ = 2 - return 1 // want "missing whitespace decreases readability" + return 1 // want `missing whitespace decreases readability \(return\)` } func fn4() int { @@ -21,7 +21,7 @@ func fn4() int { _ = 1 } - return 1 // want "missing whitespace decreases readability" + return 1 // want `missing whitespace decreases readability \(return\)` } func fn5() int { @@ -29,17 +29,17 @@ func fn5() int { _ = 1 _ = 2 - return 1 // want "missing whitespace decreases readability" + return 1 // want `missing whitespace decreases readability \(return\)` } - return 1 // want "missing whitespace decreases readability" + return 1 // want `missing whitespace decreases readability \(return\)` } func fn6() func() { _ = 1 _ = 2 - return func() { // want "missing whitespace decreases readability" + return func() { // want `missing whitespace decreases readability \(return\)` _ = 1 } } diff --git a/testdata/src/default_config/select/select.go b/testdata/src/default_config/select/select.go index 26d2680..cef333c 100644 --- a/testdata/src/default_config/select/select.go +++ b/testdata/src/default_config/select/select.go @@ -15,7 +15,7 @@ func fn1(ctx context.Context, ch1 chan struct{}) { func fn1(ctx context.Context, ch1 chan struct{}) { x := 1 - select { // want "missing whitespace decreases readability" + select { // want `missing whitespace decreases readability \(select\)` case ctx.Done(): _ = 1 case <-ch1: diff --git a/testdata/src/default_config/select/select.go.golden b/testdata/src/default_config/select/select.go.golden index a9c3786..b554ad7 100644 --- a/testdata/src/default_config/select/select.go.golden +++ b/testdata/src/default_config/select/select.go.golden @@ -16,7 +16,7 @@ func fn1(ctx context.Context, ch1 chan struct{}) { func fn1(ctx context.Context, ch1 chan struct{}) { x := 1 - select { // want "missing whitespace decreases readability" + select { // want `missing whitespace decreases readability \(select\)` case ctx.Done(): _ = 1 case <-ch1: diff --git a/testdata/src/default_config/switch/switch.go b/testdata/src/default_config/switch/switch.go index 608f56a..0159724 100644 --- a/testdata/src/default_config/switch/switch.go +++ b/testdata/src/default_config/switch/switch.go @@ -2,7 +2,7 @@ package testpkg func fn1() { a := 1 - b := 2 // want "missing whitespace decreases readability" + b := 2 // want `missing whitespace decreases readability \(switch\)` switch b { case 1: case 2: @@ -14,7 +14,7 @@ func fn1() { func fn2() { b := 2 a := 1 - switch b { // want "missing whitespace decreases readability" + switch b { // want `missing whitespace decreases readability \(switch\)` case 1: case 2: case 3: @@ -27,7 +27,7 @@ func fn3() { case true: case false: } - switch true { // want "missing whitespace decreases readability" + switch true { // want `missing whitespace decreases readability \(switch\)` case true: case false: panic("") diff --git a/testdata/src/default_config/switch/switch.go.golden b/testdata/src/default_config/switch/switch.go.golden index 0804498..ef88e63 100644 --- a/testdata/src/default_config/switch/switch.go.golden +++ b/testdata/src/default_config/switch/switch.go.golden @@ -3,7 +3,7 @@ package testpkg func fn1() { a := 1 - b := 2 // want "missing whitespace decreases readability" + b := 2 // want `missing whitespace decreases readability \(switch\)` switch b { case 1: case 2: @@ -16,7 +16,7 @@ func fn2() { b := 2 a := 1 - switch b { // want "missing whitespace decreases readability" + switch b { // want `missing whitespace decreases readability \(switch\)` case 1: case 2: case 3: @@ -30,7 +30,7 @@ func fn3() { case false: } - switch true { // want "missing whitespace decreases readability" + switch true { // want `missing whitespace decreases readability \(switch\)` case true: case false: panic("") diff --git a/testdata/src/default_config/type_switch/type_switch.go b/testdata/src/default_config/type_switch/type_switch.go index 36dc932..3bb8089 100644 --- a/testdata/src/default_config/type_switch/type_switch.go +++ b/testdata/src/default_config/type_switch/type_switch.go @@ -18,7 +18,7 @@ func fn2() { var a any = 1 b := 1 - switch a.(type) { // want "missing whitespace decreases readability" + switch a.(type) { // want `missing whitespace decreases readability \(type-switch\)` case int: case string: } @@ -41,7 +41,7 @@ func fn3(in any) { func fn4(in any) { a := Fn() - b := Fn() // want "missing whitespace decreases readability" + b := Fn() // want `missing whitespace decreases readability \(type-switch\)` switch b.(type) { case string: return diff --git a/testdata/src/default_config/type_switch/type_switch.go.golden b/testdata/src/default_config/type_switch/type_switch.go.golden index 97c5f6e..5aef16e 100644 --- a/testdata/src/default_config/type_switch/type_switch.go.golden +++ b/testdata/src/default_config/type_switch/type_switch.go.golden @@ -19,7 +19,7 @@ func fn2() { b := 1 - switch a.(type) { // want "missing whitespace decreases readability" + switch a.(type) { // want `missing whitespace decreases readability \(type-switch\)` case int: case string: } @@ -43,7 +43,7 @@ func fn3(in any) { func fn4(in any) { a := Fn() - b := Fn() // want "missing whitespace decreases readability" + b := Fn() // want `missing whitespace decreases readability \(type-switch\)` switch b.(type) { case string: return diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go b/testdata/src/default_config/whitespace/leading_whitespace.go index bb056f7..e719d34 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go +++ b/testdata/src/default_config/whitespace/leading_whitespace.go @@ -6,19 +6,19 @@ func Call(fn func()) func() { return fn } -func fn1() { // want "unnecessary whitespace decreases readability" +func fn1() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` fmt.Println("Hello, World") } -func fn2() { // want "unnecessary whitespace decreases readability" +func fn2() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` // Space before comment. fmt.Println("Hello, World") } func fn3() { - // Space after comment // want "unnecessary whitespace decreases readability" + // Space after comment // want `unnecessary whitespace decreases readability \(leading-whitespace\)` fmt.Println("Hello, World") } @@ -40,7 +40,7 @@ func fn51() { } func fn6() { - if true { // want "unnecessary whitespace decreases readability" + if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } @@ -49,56 +49,56 @@ func fn6() { func fn7() { if true { _ = 1 - } else if true { // want "unnecessary whitespace decreases readability" + } else if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 - } else { // want "unnecessary whitespace decreases readability" + } else { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } } -func fn8(a string, b any, s []string) { // want "unnecessary whitespace decreases readability" +func fn8(a string, b any, s []string) { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` - if true { // want "unnecessary whitespace decreases readability" + if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 - } else if true { // want "unnecessary whitespace decreases readability" + } else if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - for i := 0; i < 1; i++ { // want "unnecessary whitespace decreases readability" + for i := 0; i < 1; i++ { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - for n := range []int{} { // want "unnecessary whitespace decreases readability" + for n := range []int{} { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = n } - for range s { // want "unnecessary whitespace decreases readability" + for range s { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - switch a { // want "unnecessary whitespace decreases readability" + switch a { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` case "a": } - switch b.(type) { // want "unnecessary whitespace decreases readability" + switch b.(type) { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` case int: } - f := func() { // want "unnecessary whitespace decreases readability" + f := func() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - f2 := Call(func() { // want "unnecessary whitespace decreases readability" + f2 := Call(func() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 }) @@ -109,19 +109,18 @@ func fn8(a string, b any, s []string) { // want "unnecessary whitespace decrease func fn9() { switch { - case 1: // want "unnecessary whitespace decreases readability" + case 1: // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 case 2: - // This is a comment // want "unnecessary whitespace decreases readability" + // This is a comment // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 2 - default: // want "unnecessary whitespace decreases readability" + default: // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 3 } } - diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go.golden b/testdata/src/default_config/whitespace/leading_whitespace.go.golden index 4f63e20..45d79ed 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go.golden +++ b/testdata/src/default_config/whitespace/leading_whitespace.go.golden @@ -6,17 +6,17 @@ func Call(fn func()) func() { return fn } -func fn1() { // want "unnecessary whitespace decreases readability" +func fn1() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` fmt.Println("Hello, World") } -func fn2() { // want "unnecessary whitespace decreases readability" +func fn2() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` // Space before comment. fmt.Println("Hello, World") } func fn3() { - // Space after comment // want "unnecessary whitespace decreases readability" + // Space after comment // want `unnecessary whitespace decreases readability \(leading-whitespace\)` fmt.Println("Hello, World") } @@ -37,7 +37,7 @@ func fn51() { } func fn6() { - if true { // want "unnecessary whitespace decreases readability" + if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } } @@ -45,45 +45,45 @@ func fn6() { func fn7() { if true { _ = 1 - } else if true { // want "unnecessary whitespace decreases readability" + } else if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 - } else { // want "unnecessary whitespace decreases readability" + } else { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } } -func fn8(a string, b any, s []string) { // want "unnecessary whitespace decreases readability" - if true { // want "unnecessary whitespace decreases readability" +func fn8(a string, b any, s []string) { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 - } else if true { // want "unnecessary whitespace decreases readability" + } else if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - for i := 0; i < 1; i++ { // want "unnecessary whitespace decreases readability" + for i := 0; i < 1; i++ { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - for n := range []int{} { // want "unnecessary whitespace decreases readability" + for n := range []int{} { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = n } - for range s { // want "unnecessary whitespace decreases readability" + for range s { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - switch a { // want "unnecessary whitespace decreases readability" + switch a { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` case "a": } - switch b.(type) { // want "unnecessary whitespace decreases readability" + switch b.(type) { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` case int: } - f := func() { // want "unnecessary whitespace decreases readability" + f := func() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - f2 := Call(func() { // want "unnecessary whitespace decreases readability" + f2 := Call(func() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 }) @@ -93,14 +93,14 @@ func fn8(a string, b any, s []string) { // want "unnecessary whitespace decrease func fn9() { switch { - case 1: // want "unnecessary whitespace decreases readability" + case 1: // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 case 2: - // This is a comment // want "unnecessary whitespace decreases readability" + // This is a comment // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 2 - default: // want "unnecessary whitespace decreases readability" + default: // want `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 3 } diff --git a/testdata/src/default_config/whitespace/trailing_whitespace.go b/testdata/src/default_config/whitespace/trailing_whitespace.go index a62bc6a..ca6fdc4 100644 --- a/testdata/src/default_config/whitespace/trailing_whitespace.go +++ b/testdata/src/default_config/whitespace/trailing_whitespace.go @@ -9,13 +9,13 @@ func Call(fn func()) func() { func fn1() { fmt.Println("Hello, World") -} // want "unnecessary whitespace decreases readability" +} // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` func fn2() { fmt.Println("Hello, World") // Comment with wihtespace -} // want "unnecessary whitespace decreases readability" +} // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` func fn3() { fmt.Println("Hello, World") @@ -33,45 +33,45 @@ func fn5() { if true { _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` } func fn6() { if true { _ = 1 - } else if true { // want "unnecessary whitespace decreases readability" + } else if true { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` _ = 1 - } else { // want "unnecessary whitespace decreases readability" + } else { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` } func fn8(a string, b any, s []string) { if true { _ = 1 - } else if true { // want "unnecessary whitespace decreases readability" + } else if true { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` for i := 0; i < 1; i++ { _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` for n := range []int{} { _ = n - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` for range s { _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` switch a { case "a": @@ -86,12 +86,12 @@ func fn8(a string, b any, s []string) { f := func() { _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` f2 := Call(func() { _ = 1 - }) // want "unnecessary whitespace decreases readability" + }) // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` _ = f _ = f2 diff --git a/testdata/src/default_config/whitespace/trailing_whitespace.go.golden b/testdata/src/default_config/whitespace/trailing_whitespace.go.golden index 0814499..13b7429 100644 --- a/testdata/src/default_config/whitespace/trailing_whitespace.go.golden +++ b/testdata/src/default_config/whitespace/trailing_whitespace.go.golden @@ -8,12 +8,12 @@ func Call(fn func()) func() { func fn1() { fmt.Println("Hello, World") -} // want "unnecessary whitespace decreases readability" +} // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` func fn2() { fmt.Println("Hello, World") // Comment with wihtespace -} // want "unnecessary whitespace decreases readability" +} // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` func fn3() { fmt.Println("Hello, World") @@ -30,37 +30,37 @@ func fn4() { func fn5() { if true { _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` } func fn6() { if true { _ = 1 - } else if true { // want "unnecessary whitespace decreases readability" + } else if true { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` _ = 1 - } else { // want "unnecessary whitespace decreases readability" + } else { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` } func fn8(a string, b any, s []string) { if true { _ = 1 - } else if true { // want "unnecessary whitespace decreases readability" + } else if true { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` for i := 0; i < 1; i++ { _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` for n := range []int{} { _ = n - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` for range s { _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` switch a { case "a": @@ -74,11 +74,11 @@ func fn8(a string, b any, s []string) { f := func() { _ = 1 - } // want "unnecessary whitespace decreases readability" + } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` f2 := Call(func() { _ = 1 - }) // want "unnecessary whitespace decreases readability" + }) // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` _ = f _ = f2 diff --git a/testdata/src/with_config/case_trailing_whitespace/case.go b/testdata/src/with_config/case_trailing_whitespace/case.go index 545734f..21e6520 100644 --- a/testdata/src/with_config/case_trailing_whitespace/case.go +++ b/testdata/src/with_config/case_trailing_whitespace/case.go @@ -12,7 +12,7 @@ func fn1(n int) { case 3: n++ n++ - n++ // want "missing whitespace decreases readability" + n++ // want `missing whitespace decreases readability \(case-trailing-newline\)` case 4: n++ n++ @@ -33,7 +33,7 @@ func fn2(ctx context.Context, ch1 chan struct{}) { _ = 1 _ = 1 _ = 1 - _ = 1 // want "missing whitespace decreases readability" + _ = 1 // want `missing whitespace decreases readability \(case-trailing-newline\)` case <-ch1: _ = 1 default: diff --git a/testdata/src/with_config/case_trailing_whitespace/case.go.golden b/testdata/src/with_config/case_trailing_whitespace/case.go.golden index 08e2e4c..394947f 100644 --- a/testdata/src/with_config/case_trailing_whitespace/case.go.golden +++ b/testdata/src/with_config/case_trailing_whitespace/case.go.golden @@ -12,7 +12,7 @@ func fn1(n int) { case 3: n++ n++ - n++ // want "missing whitespace decreases readability" + n++ // want `missing whitespace decreases readability \(case-trailing-newline\)` case 4: n++ @@ -34,7 +34,7 @@ func fn2(ctx context.Context, ch1 chan struct{}) { _ = 1 _ = 1 _ = 1 - _ = 1 // want "missing whitespace decreases readability" + _ = 1 // want `missing whitespace decreases readability \(case-trailing-newline\)` case <-ch1: _ = 1 diff --git a/testdata/src/with_config/exclusive_short_decl/code.go b/testdata/src/with_config/exclusive_short_decl/code.go index f5e1039..1c7755d 100644 --- a/testdata/src/with_config/exclusive_short_decl/code.go +++ b/testdata/src/with_config/exclusive_short_decl/code.go @@ -10,6 +10,6 @@ func fn() { c = 1 d := 1 - a = 3 // want "missing whitespace decreases readability" - e := 4 // want "missing whitespace decreases readability" + a = 3 // want `missing whitespace decreases readability \(assign\)` + e := 4 // want `missing whitespace decreases readability \(assign\)` } diff --git a/testdata/src/with_config/exclusive_short_decl/code.go.golden b/testdata/src/with_config/exclusive_short_decl/code.go.golden index 3dcced7..3c2e11f 100644 --- a/testdata/src/with_config/exclusive_short_decl/code.go.golden +++ b/testdata/src/with_config/exclusive_short_decl/code.go.golden @@ -11,7 +11,7 @@ func fn() { d := 1 - a = 3 // want "missing whitespace decreases readability" + a = 3 // want `missing whitespace decreases readability \(assign\)` - e := 4 // want "missing whitespace decreases readability" + e := 4 // want `missing whitespace decreases readability \(assign\)` } diff --git a/testdata/src/with_config/first_in_block_n1/block.go b/testdata/src/with_config/first_in_block_n1/block.go index b127703..b1cfd1a 100644 --- a/testdata/src/with_config/first_in_block_n1/block.go +++ b/testdata/src/with_config/first_in_block_n1/block.go @@ -13,7 +13,7 @@ func fn1() { } b := 1 - if true { // want "missing whitespace decreases readability" + if true { // want `missing whitespace decreases readability \(if\)` Fn(one) if false { diff --git a/testdata/src/with_config/first_in_block_n1/block.go.golden b/testdata/src/with_config/first_in_block_n1/block.go.golden index 2b3f3c0..6d0801a 100644 --- a/testdata/src/with_config/first_in_block_n1/block.go.golden +++ b/testdata/src/with_config/first_in_block_n1/block.go.golden @@ -14,7 +14,7 @@ func fn1() { b := 1 - if true { // want "missing whitespace decreases readability" + if true { // want `missing whitespace decreases readability \(if\)` Fn(one) if false { diff --git a/testdata/src/with_config/if_errcheck/if_errcheck.go b/testdata/src/with_config/if_errcheck/if_errcheck.go index 66c7a70..030c8be 100644 --- a/testdata/src/with_config/if_errcheck/if_errcheck.go +++ b/testdata/src/with_config/if_errcheck/if_errcheck.go @@ -5,7 +5,7 @@ import "errors" func fn1() { err := errors.New("x") - if err != nil { // want "unnecessary whitespace decreases readability" + if err != nil { // want `unnecessary whitespace decreases readability \(err\)` panic(err) } } @@ -14,7 +14,7 @@ func fn2() { a := 1 err := errors.New("x") - if err != nil { // want "unnecessary whitespace decreases readability" + if err != nil { // want `unnecessary whitespace decreases readability \(err\)` panic(err) } @@ -23,7 +23,7 @@ func fn2() { func fn3() { a := 1 - err := errors.New("x") // want "missing whitespace decreases readability" + err := errors.New("x") // want `missing whitespace decreases readability \(if\)` if err != nil { panic(err) } diff --git a/testdata/src/with_config/if_errcheck/if_errcheck.go.golden b/testdata/src/with_config/if_errcheck/if_errcheck.go.golden index 642cc78..10ee2fa 100644 --- a/testdata/src/with_config/if_errcheck/if_errcheck.go.golden +++ b/testdata/src/with_config/if_errcheck/if_errcheck.go.golden @@ -5,7 +5,7 @@ import "errors" func fn1() { err := errors.New("x") - if err != nil { // want "unnecessary whitespace decreases readability" + if err != nil { // want `unnecessary whitespace decreases readability \(err\)` panic(err) } } @@ -14,7 +14,7 @@ func fn2() { a := 1 err := errors.New("x") - if err != nil { // want "unnecessary whitespace decreases readability" + if err != nil { // want `unnecessary whitespace decreases readability \(err\)` panic(err) } @@ -24,7 +24,7 @@ func fn2() { func fn3() { a := 1 - err := errors.New("x") // want "missing whitespace decreases readability" + err := errors.New("x") // want `missing whitespace decreases readability \(if\)` if err != nil { panic(err) } diff --git a/testdata/src/with_config/no_check_decl/no_check_decl.go.golden b/testdata/src/with_config/no_check_decl/no_check_decl.go.golden deleted file mode 100644 index 330adfe..0000000 --- a/testdata/src/with_config/no_check_decl/no_check_decl.go.golden +++ /dev/null @@ -1,23 +0,0 @@ -package testpkg - -import "fmt" - -func Fn() {} - -func fn1() { - a := 1 - if true { - _ = 1 - fmt.Println(a) - } - - b := 2 - for range make([]int, 10) { - _ = 1 - if false { - Fn() - - Fn(b) - } - } -} diff --git a/testdata/src/with_config/send/send.go b/testdata/src/with_config/send/send.go index ee6dd1c..731f6ec 100644 --- a/testdata/src/with_config/send/send.go +++ b/testdata/src/with_config/send/send.go @@ -2,10 +2,10 @@ package testpkg func fn1(ch chan int) { a := 1 - ch <- 1 // want "missing whitespace decreases readability" + ch <- 1 // want `missing whitespace decreases readability \(send\)` b := 2 - <-ch // want "missing whitespace decreases readability" + <-ch // want `missing whitespace decreases readability \(expr\)` } func fn2(ch chan int) { diff --git a/testdata/src/with_config/send/send.go.golden b/testdata/src/with_config/send/send.go.golden index 404e7c8..b6fb302 100644 --- a/testdata/src/with_config/send/send.go.golden +++ b/testdata/src/with_config/send/send.go.golden @@ -3,11 +3,11 @@ package testpkg func fn1(ch chan int) { a := 1 - ch <- 1 // want "missing whitespace decreases readability" + ch <- 1 // want `missing whitespace decreases readability \(send\)` b := 2 - <-ch // want "missing whitespace decreases readability" + <-ch // want `missing whitespace decreases readability \(expr\)` } func fn2(ch chan int) { diff --git a/testdata/src/with_config/whole_block/block.go b/testdata/src/with_config/whole_block/block.go index 9eceb10..90168b7 100644 --- a/testdata/src/with_config/whole_block/block.go +++ b/testdata/src/with_config/whole_block/block.go @@ -8,7 +8,7 @@ func fn1() { y, z := 1, 2 if false { z++ - if true { // want "missing whitespace decreases readability" + if true { // want `missing whitespace decreases readability \(if\)` y++ x++ } @@ -60,7 +60,7 @@ func fn3(a, b, c itn) { } b = 1 - for range make([]int, 10) { // want "missing whitespace decreases readability" + for range make([]int, 10) { // want `missing whitespace decreases readability \(range\)` if true { c++ @@ -78,7 +78,7 @@ func fn3(a, b, c itn) { } b = 1 - for i := 0; i < 3; i++ { // want "missing whitespace decreases readability" + for i := 0; i < 3; i++ { // want `missing whitespace decreases readability \(for\)` if true { c++ @@ -99,7 +99,7 @@ func fn4(a, b, c int) { } b = 1 - switch { // want "missing whitespace decreases readability" + switch { // want `missing whitespace decreases readability \(switch\)` case true: c++ a++ @@ -117,7 +117,7 @@ func fn5(a, b, c int) { }() b = 1 - go func() { // want "missing whitespace decreases readability" + go func() { // want `missing whitespace decreases readability \(go\)` c++ a++ }() @@ -133,7 +133,7 @@ func fn6(a, b, c int) { }() b = 1 - defer func() { // want "missing whitespace decreases readability" + defer func() { // want `missing whitespace decreases readability \(defer\)` c++ a++ }() diff --git a/testdata/src/with_config/whole_block/block.go.golden b/testdata/src/with_config/whole_block/block.go.golden index 7476b50..8dc5764 100644 --- a/testdata/src/with_config/whole_block/block.go.golden +++ b/testdata/src/with_config/whole_block/block.go.golden @@ -9,7 +9,7 @@ func fn1() { if false { z++ - if true { // want "missing whitespace decreases readability" + if true { // want `missing whitespace decreases readability \(if\)` y++ x++ } @@ -62,7 +62,7 @@ func fn3(a, b, c itn) { b = 1 - for range make([]int, 10) { // want "missing whitespace decreases readability" + for range make([]int, 10) { // want `missing whitespace decreases readability \(range\)` if true { c++ @@ -81,7 +81,7 @@ func fn3(a, b, c itn) { b = 1 - for i := 0; i < 3; i++ { // want "missing whitespace decreases readability" + for i := 0; i < 3; i++ { // want `missing whitespace decreases readability \(for\)` if true { c++ @@ -103,7 +103,7 @@ func fn4(a, b, c int) { b = 1 - switch { // want "missing whitespace decreases readability" + switch { // want `missing whitespace decreases readability \(switch\)` case true: c++ a++ @@ -122,7 +122,7 @@ func fn5(a, b, c int) { b = 1 - go func() { // want "missing whitespace decreases readability" + go func() { // want `missing whitespace decreases readability \(go\)` c++ a++ }() @@ -139,7 +139,7 @@ func fn6(a, b, c int) { b = 1 - defer func() { // want "missing whitespace decreases readability" + defer func() { // want `missing whitespace decreases readability \(defer\)` c++ a++ }() From 51b3f9f8210b73f528e491ff9b5b315c47b542e4 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 3 May 2025 23:36:15 +0200 Subject: [PATCH 068/112] Use preset instead of two bools --- analyzer.go | 12 +++++------- config.go | 26 +++++++++++--------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/analyzer.go b/analyzer.go index 0a3e30b..443db79 100644 --- a/analyzer.go +++ b/analyzer.go @@ -31,10 +31,9 @@ type wslAnalyzer struct { // When we use flags, we need to parse the ones used for checks into // temporary variables so we can create the check set once the flag is being // parsed by the analyzer and we run our analyzer. - enableAll bool - disableAll bool - enable []string - disable []string + preset string + enable []string + disable []string // To only validate and convert the parsed flags once we use a `sync.Once` // to only create a check set once and store the set and potential error. We @@ -62,8 +61,7 @@ func (wa *wslAnalyzer) flags() flag.FlagSet { flags.IntVar(&wa.config.BranchMaxLines, "branch-max-lines", 2, "Max lines before requiring newline before branching, e.g. `return`, `break`, `continue` (0 = never)") flags.IntVar(&wa.config.CaseMaxLines, "case-max-lines", 0, "Max lines before requiring a newline at the end of case (0 = never)") - flags.BoolVar(&wa.enableAll, "enable-all", false, "Enable all checks") - flags.BoolVar(&wa.disableAll, "disable-all", false, "Disable all checks") + flags.StringVar(&wa.preset, "preset", "", "Can be 'all' for all checks or 'none' for no checks") flags.Var(&multiStringValue{slicePtr: &wa.enable}, "enable", "Comma separated list of checks to enable") flags.Var(&multiStringValue{slicePtr: &wa.disable}, "disable", "Comma separated list of checks to disable") @@ -79,7 +77,7 @@ func (wa *wslAnalyzer) run(pass *analysis.Pass) (any, error) { } // Parse the check params once if we set our config from flags. - wa.config.Checks, wa.checkSetErr = NewCheckSet(wa.enableAll, wa.disableAll, wa.enable, wa.disable) + wa.config.Checks, wa.checkSetErr = NewCheckSet(wa.preset, wa.enable, wa.disable) }) if wa.checkSetErr != nil { diff --git a/config.go b/config.go index e7989d1..8969100 100644 --- a/config.go +++ b/config.go @@ -1,7 +1,6 @@ package wsl import ( - "errors" "fmt" "strings" ) @@ -133,12 +132,11 @@ func NewConfig() *Configuration { } func NewWithChecks( - enableAll bool, - disableAll bool, + preset string, enable []string, disable []string, ) (*Configuration, error) { - checks, err := NewCheckSet(enableAll, disableAll, enable, disable) + checks, err := NewCheckSet(preset, enable, disable) if err != nil { return nil, fmt.Errorf("failed to create config: %w", err) } @@ -150,23 +148,21 @@ func NewWithChecks( } func NewCheckSet( - enableAll bool, - disableAll bool, + preset string, enable []string, disable []string, ) (CheckSet, error) { - cs := DefaultChecks() + var cs CheckSet - if enableAll && disableAll { - return nil, errors.New("can't use both `enable-all` and `disable-all`") - } - - if enableAll { + switch strings.ToLower(preset) { + case "": + cs = DefaultChecks() + case "all": cs = AllChecks() - } - - if disableAll { + case "none": cs = NoChecks() + default: + return nil, fmt.Errorf("invalid preset '%s', must be `all`, `none` or `` (empty)", preset) } for _, s := range enable { From a6cc700168cb699fef11a566e28300d620e5731f Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 3 May 2025 23:43:35 +0200 Subject: [PATCH 069/112] Rename preset to default to align with `golangci-lint` --- README.md | 3 +-- analyzer.go | 10 +++++----- config.go | 10 +++++----- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 99d6cda..3146fd2 100644 --- a/README.md +++ b/README.md @@ -129,8 +129,7 @@ linters: allow-whole-block: false branch-max-lines: 2 case-max-lines: 0 - enable-all: false - disable-all: false + default: none enable: - assign - branch diff --git a/analyzer.go b/analyzer.go index 443db79..782fee1 100644 --- a/analyzer.go +++ b/analyzer.go @@ -31,9 +31,9 @@ type wslAnalyzer struct { // When we use flags, we need to parse the ones used for checks into // temporary variables so we can create the check set once the flag is being // parsed by the analyzer and we run our analyzer. - preset string - enable []string - disable []string + defaultChecks string + enable []string + disable []string // To only validate and convert the parsed flags once we use a `sync.Once` // to only create a check set once and store the set and potential error. We @@ -61,7 +61,7 @@ func (wa *wslAnalyzer) flags() flag.FlagSet { flags.IntVar(&wa.config.BranchMaxLines, "branch-max-lines", 2, "Max lines before requiring newline before branching, e.g. `return`, `break`, `continue` (0 = never)") flags.IntVar(&wa.config.CaseMaxLines, "case-max-lines", 0, "Max lines before requiring a newline at the end of case (0 = never)") - flags.StringVar(&wa.preset, "preset", "", "Can be 'all' for all checks or 'none' for no checks") + flags.StringVar(&wa.defaultChecks, "default", "", "Can be 'all' for all checks or 'none' for no checks or empty for default checks") flags.Var(&multiStringValue{slicePtr: &wa.enable}, "enable", "Comma separated list of checks to enable") flags.Var(&multiStringValue{slicePtr: &wa.disable}, "disable", "Comma separated list of checks to disable") @@ -77,7 +77,7 @@ func (wa *wslAnalyzer) run(pass *analysis.Pass) (any, error) { } // Parse the check params once if we set our config from flags. - wa.config.Checks, wa.checkSetErr = NewCheckSet(wa.preset, wa.enable, wa.disable) + wa.config.Checks, wa.checkSetErr = NewCheckSet(wa.defaultChecks, wa.enable, wa.disable) }) if wa.checkSetErr != nil { diff --git a/config.go b/config.go index 8969100..c1e77ee 100644 --- a/config.go +++ b/config.go @@ -132,11 +132,11 @@ func NewConfig() *Configuration { } func NewWithChecks( - preset string, + defaultChecks string, enable []string, disable []string, ) (*Configuration, error) { - checks, err := NewCheckSet(preset, enable, disable) + checks, err := NewCheckSet(defaultChecks, enable, disable) if err != nil { return nil, fmt.Errorf("failed to create config: %w", err) } @@ -148,13 +148,13 @@ func NewWithChecks( } func NewCheckSet( - preset string, + defaultChecks string, enable []string, disable []string, ) (CheckSet, error) { var cs CheckSet - switch strings.ToLower(preset) { + switch strings.ToLower(defaultChecks) { case "": cs = DefaultChecks() case "all": @@ -162,7 +162,7 @@ func NewCheckSet( case "none": cs = NoChecks() default: - return nil, fmt.Errorf("invalid preset '%s', must be `all`, `none` or `` (empty)", preset) + return nil, fmt.Errorf("invalid preset '%s', must be `all`, `none` or `` (empty)", defaultChecks) } for _, s := range enable { From 5982d50818e32acf546f1dc8b4489eb034028009 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 4 May 2025 19:27:41 +0200 Subject: [PATCH 070/112] Add migration tool for `.golangci.yml` --- cmd/golangci-lint-migrate/README.md | 59 +++++++ cmd/golangci-lint-migrate/main.go | 183 ++++++++++++++++++++ cmd/golangci-lint-migrate/v1.yaml | 78 +++++++++ cmd/golangci-lint-migrate/v2.yaml | 85 +++++++++ go.mod | 1 + go.sum | 2 + testdata/src/default_config/v1/v1.go | 12 ++ testdata/src/default_config/v1/v1.go.golden | 13 ++ 8 files changed, 433 insertions(+) create mode 100644 cmd/golangci-lint-migrate/README.md create mode 100644 cmd/golangci-lint-migrate/main.go create mode 100644 cmd/golangci-lint-migrate/v1.yaml create mode 100644 cmd/golangci-lint-migrate/v2.yaml create mode 100644 testdata/src/default_config/v1/v1.go create mode 100644 testdata/src/default_config/v1/v1.go.golden diff --git a/cmd/golangci-lint-migrate/README.md b/cmd/golangci-lint-migrate/README.md new file mode 100644 index 0000000..1c340ad --- /dev/null +++ b/cmd/golangci-lint-migrate/README.md @@ -0,0 +1,59 @@ +# Configuration migrator + +This is a tool that helps you migrate from an existing `golangci-lint` +configuration to the new `v5` configuration. + +The biggest change is that `v5` uses checks for everything that can be turned on +and off individually. + +## Usage + +Just run the code and point it to your `.golangci.yml` file. + +```sh +golangci-lint-migrate .golangci.yml + +2025/05/04 19:28:11 `allow-trailing-comment` is deprecated and always allowed in >= v5 +2025/05/04 19:28:11 `allow-separated-leading-comment` is deprecated and always allowed in >= v5 + +These settings are the closest you can get in the new version of `wsl` +Potential deprecations are logged above + +See https://github.com/bombsimon/wsl for more details + +linters: + settings: + wsl: + allow-first-in-block: true + allow-whole-block: false + branch-max-lines: 2 + case-max-lines: 1 + enable: + - assign-exclusive + - err + disable: + - append + - assign + - decl +``` + +## Info + +See [repo] and [rules] for details but in short, this is the change: + +- **strict-append** - Converted to a check called `append` +- **allow-assign-and-call** - Deprecated +- **allow-assign-and-anything** - Converted to a check called `assign` +- **allow-multiline-assign** - Deprecated, always allowed +- **force-case-trailing-whitespace** - Renamed to `case-max-lines` +- **allow-trailing-comment** - Deprecated, always allowed +- **allow-separated-leading-comment** - Deprecated, always allowed +- **allow-cuddle-declarations** - Converted to a check called `decl` +- **allow-cuddle-with-calls** - Deprecated and not needed +- **allow-cuddle-with-rhs** - Deprecated and not needed +- **force-err-cuddling** - Converted to a check called `err` +- **error-variable-names** - Deprecated, we allow everything impl. `error` +- **force-short-decl-cuddling** - Converted to a check called `assign-exclusive` + +[repo]: https://github.com/bombsimon/wsl/blob/main/README.md +[rules]: https://github.com/bombsimon/wsl/blob/main/RULES.md diff --git a/cmd/golangci-lint-migrate/main.go b/cmd/golangci-lint-migrate/main.go new file mode 100644 index 0000000..069cfef --- /dev/null +++ b/cmd/golangci-lint-migrate/main.go @@ -0,0 +1,183 @@ +package main + +import ( + "bytes" + "fmt" + "log" + "os" + "slices" + + "github.com/bombsimon/wsl/v4" + "gopkg.in/yaml.v3" +) + +type Config struct { + Version string `yaml:"version"` +} + +type V1ToV4 struct { + Settings Settings `yaml:"linters-settings"` +} + +type V5 struct { + Linters Linters `yaml:"linters"` +} + +type Linters struct { + Settings Settings `yaml:"settings"` +} + +type Settings struct { + WSL *WSL `yaml:"wsl"` +} + +type NewConfig struct { + Linters struct { + Settings struct { + WSL WSLV5 `yaml:"wsl"` + } `yaml:"settings"` + } `yaml:"linters"` +} + +type WSL struct { + StrictAppend bool `yaml:"strict-append"` + AllowAssignAndCall bool `yaml:"allow-assign-and-call"` + AllowAssignAndAnything bool `yaml:"allow-assign-and-anything"` + AllowMultilineAssign bool `yaml:"allow-multiline-assign"` + AllowTrailingComment bool `yaml:"allow-trailing-comment"` + AllowCuddleDeclarations bool `yaml:"allow-cuddle-declarations"` + AllowSeparatedLeadingComment bool `yaml:"allow-separated-leading-comment"` + ForceErrCuddling bool `yaml:"force-err-cuddling"` + ForceShortDeclCuddling bool `yaml:"force-short-decl-cuddling"` + ForceCaseTrailingWhitespace int `yaml:"force-case-trailing-whitespace"` + + AllowCuddlingWithCalls []string `yaml:"allow-cuddle-with-calls"` + AllowCuddleWithRhs []string `yaml:"allow-cuddle-with-rhs"` + ErrorVariableNames []string `yaml:"error-variable-names"` +} + +type WSLV5 struct { + AllowFirstInBlock bool `yaml:"allow-first-in-block"` + AllowWholeBlock bool `yaml:"allow-whole-block"` + BranchMaxLines int `yaml:"branch-max-lines"` + CaseMaxLines int `yaml:"case-max-lines"` + Enable []string `yaml:"enable"` + Disable []string `yaml:"disable"` +} + +func main() { + if len(os.Args) != 2 { + log.Fatalf("expected exactly one argument which should be your `.golangci.yml` file") + } + + v1cfg := getWslConfig(os.Args[1]) + if v1cfg == nil { + log.Println("failed to get wsl configuration from config") + log.Println("ensure you passed a configuration with existing `wsl` configuration") + log.Fatalln("if you didn't use any configuration you can keep leaving it empty to use the defaults") + } + + v5cfg := WSLV5{ + AllowFirstInBlock: true, + AllowWholeBlock: false, + BranchMaxLines: 2, + CaseMaxLines: v1cfg.ForceCaseTrailingWhitespace, + } + + if !v1cfg.StrictAppend { + v5cfg.Disable = append(v5cfg.Disable, wsl.CheckAppend.String()) + } + + if v1cfg.AllowAssignAndCall { + log.Println("`allow-assign-and-call` is deprecated in >= v5, you can disable assign checks completely") + } + + if v1cfg.AllowAssignAndAnything { + v5cfg.Disable = append(v5cfg.Disable, wsl.CheckAssign.String()) + } + + if v1cfg.AllowMultilineAssign { + log.Println("`allow-multiline-assign` is deprecated and always allowed in >= v5") + } + + if v1cfg.AllowTrailingComment { + log.Println("`allow-trailing-comment` is deprecated and always allowed in >= v5") + } + + if v1cfg.AllowSeparatedLeadingComment { + log.Println("`allow-separated-leading-comment` is deprecated and always allowed in >= v5") + } + + if v1cfg.AllowCuddleDeclarations { + v5cfg.Disable = append(v5cfg.Disable, wsl.CheckDecl.String()) + } + + if v1cfg.ForceErrCuddling { + v5cfg.Enable = append(v5cfg.Enable, wsl.CheckErr.String()) + } + + if v1cfg.ForceShortDeclCuddling { + v5cfg.Enable = append(v5cfg.Enable, wsl.CheckAssignExclusive.String()) + } + + // These are deprecated and not needed in v5 + // v1cfg.AllowCuddlingWithCalls + // v1cfg.AllowCuddleWithRhs + // v1cfg.ErrorVariableNames + + slices.Sort(v5cfg.Enable) + slices.Sort(v5cfg.Disable) + + cfg := NewConfig{} + cfg.Linters.Settings.WSL = v5cfg + + buf := bytes.NewBuffer([]byte{}) + e := yaml.NewEncoder(buf) + e.SetIndent(2) + + if err := e.Encode(&cfg); err != nil { + log.Fatalf("%v", err) + } + + fmt.Println("") + fmt.Println("These settings are the closest you can get in the new version of `wsl`") + fmt.Println("Potential deprecations are logged above") + fmt.Println("") + fmt.Println("See https://github.com/bombsimon/wsl for more details") + fmt.Println("") + + fmt.Println(buf.String()) +} + +func getWslConfig(filename string) *WSL { + yamlFile, err := os.ReadFile(filename) + if err != nil { + log.Fatalf("%v", err) + } + + var cfg Config + if err := yaml.Unmarshal(yamlFile, &cfg); err != nil { + log.Fatalf("%v", err) + } + + switch cfg.Version { + case "": + var cfg V1ToV4 + if err := yaml.Unmarshal(yamlFile, &cfg); err != nil { + log.Fatalf("%v", err) + } + + return cfg.Settings.WSL + case "2": + var cfg V5 + if err := yaml.Unmarshal(yamlFile, &cfg); err != nil { + log.Fatalf("%v", err) + } + + return cfg.Linters.Settings.WSL + default: + log.Fatalf("invalid version '%s'", cfg.Version) + } + + return nil +} diff --git a/cmd/golangci-lint-migrate/v1.yaml b/cmd/golangci-lint-migrate/v1.yaml new file mode 100644 index 0000000..537380f --- /dev/null +++ b/cmd/golangci-lint-migrate/v1.yaml @@ -0,0 +1,78 @@ +--- +linters: + enable: + - wsl + +linters-settings: + wsl: + # Do strict checking when assigning from append (x = append(x, y)). + # If this is set to true - the append call must append either a variable + # assigned, called or used on the line above. + # https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#strict-append + # Default: true + strict-append: false + + # Allows assignments to be cuddled with variables used in calls on + # line above and calls to be cuddled with assignments of variables + # used in call on line above. + # https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#allow-assign-and-call + # Default: true + allow-assign-and-call: false + + # Allows assignments to be cuddled with anything. + # https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#allow-assign-and-anything + # Default: false + allow-assign-and-anything: true + + # Allows cuddling to assignments even if they span over multiple lines. + # https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#allow-multiline-assign + # Default: true + allow-multiline-assign: false + + # If the number of lines in a case block is equal to or lager than this number, + # the case *must* end white a newline. + # https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#force-case-trailing-whitespace + # Default: 0 + force-case-trailing-whitespace: 1 + + # Allow blocks to end with comments. + # https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#allow-trailing-comment + # Default: false + allow-trailing-comment: true + + # Allow multiple comments in the beginning of a block separated with newline. + # https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#allow-separated-leading-comment + # Default: false + allow-separated-leading-comment: true + + # Allow multiple var/declaration statements to be cuddled. + # https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#allow-cuddle-declarations + # Default: false + allow-cuddle-declarations: true + + # A list of call idents that everything can be cuddled with. + # Defaults: [ "Lock", "RLock" ] + allow-cuddle-with-calls: ["Foo", "Bar"] + + # AllowCuddleWithRHS is a list of right hand side variables that is allowed + # to be cuddled with anything. + # Defaults: [ "Unlock", "RUnlock" ] + allow-cuddle-with-rhs: ["Foo", "Bar"] + + # Causes an error when an If statement that checks an error variable doesn't + # cuddle with the assignment of that variable. + # https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#force-err-cuddling + # Default: false + force-err-cuddling: true + + # When force-err-cuddling is enabled this is a list of names + # used for error variables to check for in the conditional. + # Default: [ "err" ] + error-variable-names: ["foo"] + + # Causes an error if a short declaration (:=) cuddles with anything other than + # another short declaration. + # This logic overrides force-err-cuddling among others. + # https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#force-short-decl-cuddling + # Default: false + force-short-decl-cuddling: true diff --git a/cmd/golangci-lint-migrate/v2.yaml b/cmd/golangci-lint-migrate/v2.yaml new file mode 100644 index 0000000..f7dc97f --- /dev/null +++ b/cmd/golangci-lint-migrate/v2.yaml @@ -0,0 +1,85 @@ +--- +version: "2" + +linters: + enable: + - wsl + settings: + wsl: + # Do strict checking when assigning from append (x = append(x, y)). + # If this is set to true - the append call must append either a variable + # assigned, called or used on the line above. + # https://github.com/bombsimon/wsl/blob/HEAD/doc/configuration.md#strict-append + # Default: true + strict-append: false + + # Allows assignments to be cuddled with variables used in calls on + # line above and calls to be cuddled with assignments of variables + # used in call on line above. + # https://github.com/bombsimon/wsl/blob/HEAD/doc/configuration.md#allow-assign-and-call + # Default: true + allow-assign-and-call: false + + # Allows assignments to be cuddled with anything. + # https://github.com/bombsimon/wsl/blob/HEAD/doc/configuration.md#allow-assign-and-anything + # Default: false + allow-assign-and-anything: true + + # Allows cuddling to assignments even if they span over multiple lines. + # https://github.com/bombsimon/wsl/blob/HEAD/doc/configuration.md#allow-multiline-assign + # Default: true + allow-multiline-assign: false + + # If the number of lines in a case block is equal to or lager than this number, + # the case *must* end white a newline. + # https://github.com/bombsimon/wsl/blob/HEAD/doc/configuration.md#force-case-trailing-whitespace + # Default: 0 + force-case-trailing-whitespace: 1 + + # Allow blocks to end with comments. + # https://github.com/bombsimon/wsl/blob/HEAD/doc/configuration.md#allow-trailing-comment + # Default: false + allow-trailing-comment: true + + # Allow multiple comments in the beginning of a block separated with newline. + # https://github.com/bombsimon/wsl/blob/HEAD/doc/configuration.md#allow-separated-leading-comment + # Default: false + allow-separated-leading-comment: true + + # Allow multiple var/declaration statements to be cuddled. + # https://github.com/bombsimon/wsl/blob/HEAD/doc/configuration.md#allow-cuddle-declarations + # Default: false + allow-cuddle-declarations: true + + # A list of call idents that everything can be cuddled with. + # Defaults: [ "Lock", "RLock" ] + allow-cuddle-with-calls: ["Foo", "Bar"] + + # AllowCuddleWithRHS is a list of right hand side variables that is allowed + # to be cuddled with anything. + # Defaults: [ "Unlock", "RUnlock" ] + allow-cuddle-with-rhs: ["Foo", "Bar"] + + # Allow cuddling with any block as long as the variable is used somewhere in + # the block. + # https://github.com/bombsimon/wsl/blob/HEAD/doc/configuration.md#allow-cuddle-used-in-block + # Default: false + allow-cuddle-used-in-block: true + + # Causes an error when an If statement that checks an error variable doesn't + # cuddle with the assignment of that variable. + # https://github.com/bombsimon/wsl/blob/HEAD/doc/configuration.md#force-err-cuddling + # Default: false + force-err-cuddling: true + + # When force-err-cuddling is enabled this is a list of names + # used for error variables to check for in the conditional. + # Default: [ "err" ] + error-variable-names: ["foo"] + + # Causes an error if a short declaration (:=) cuddles with anything other than + # another short declaration. + # This logic overrides force-err-cuddling among others. + # https://github.com/bombsimon/wsl/blob/HEAD/doc/configuration.md#force-short-decl-cuddling + # Default: false + force-short-decl-cuddling: true diff --git a/go.mod b/go.mod index b881d50..bd38ca0 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24 require ( github.com/stretchr/testify v1.9.0 golang.org/x/tools v0.32.0 + gopkg.in/yaml.v2 v2.4.0 ) require ( diff --git a/go.sum b/go.sum index b2e79ca..e9d6a2e 100644 --- a/go.sum +++ b/go.sum @@ -14,5 +14,7 @@ golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/testdata/src/default_config/v1/v1.go b/testdata/src/default_config/v1/v1.go new file mode 100644 index 0000000..7d4b57f --- /dev/null +++ b/testdata/src/default_config/v1/v1.go @@ -0,0 +1,12 @@ +package testdata + +func fn1(s map[string]bool) { + // TODO: Regression v1 + timeout := 10 + switch { // want `missing whitespace decreases readability` + case s["bad timeout"]: + timeout = 100 + case s["zero timeout"]: + timeout = 0 + } +} diff --git a/testdata/src/default_config/v1/v1.go.golden b/testdata/src/default_config/v1/v1.go.golden new file mode 100644 index 0000000..bb76cac --- /dev/null +++ b/testdata/src/default_config/v1/v1.go.golden @@ -0,0 +1,13 @@ +package testdata + +func fn1(s map[string]bool) { + // TODO: Regression v1 + timeoutx := 10 + + switch { // want `missing whitespace decreases readability` + case s["bad timeout"]: + timeout = 100 + case s["zero timeout"]: + timeout = 0 + } +} From 04b6483623750a87e819e0bf5ce7d91b9908c9ec Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 4 May 2025 19:32:59 +0200 Subject: [PATCH 071/112] Use more sensible default in example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3146fd2..54beea8 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ linters: allow-whole-block: false branch-max-lines: 2 case-max-lines: 0 - default: none + default: ~ # Can be `all`, `none` or empty enable: - assign - branch From 6af122bd0711c217c1dc2d1cb03a5222d22a5951 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 5 May 2025 21:09:50 +0200 Subject: [PATCH 072/112] Report leading and trailing whitespace on the empty line --- .../whitespace/leading_whitespace.go | 42 +++++++-------- .../whitespace/leading_whitespace.go.golden | 42 +++++++-------- .../whitespace/trailing_whitespace.go | 52 +++++++++--------- .../whitespace/trailing_whitespace.go.golden | 53 +++++++++---------- wsl.go | 4 +- 5 files changed, 96 insertions(+), 97 deletions(-) diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go b/testdata/src/default_config/whitespace/leading_whitespace.go index e719d34..bf6f0ad 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go +++ b/testdata/src/default_config/whitespace/leading_whitespace.go @@ -6,19 +6,19 @@ func Call(fn func()) func() { return fn } -func fn1() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn1() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` fmt.Println("Hello, World") } -func fn2() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn2() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` // Space before comment. fmt.Println("Hello, World") } -func fn3() { - // Space after comment // want `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn3() { // want +2 `unnecessary whitespace decreases readability \(leading-whitespace\)` + // Space after comment fmt.Println("Hello, World") } @@ -40,7 +40,7 @@ func fn51() { } func fn6() { - if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } @@ -49,56 +49,56 @@ func fn6() { func fn7() { if true { _ = 1 - } else if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 - } else { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } } -func fn8(a string, b any, s []string) { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn8(a string, b any, s []string) { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` - if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 - } else if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - for i := 0; i < 1; i++ { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + for i := 0; i < 1; i++ { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - for n := range []int{} { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + for n := range []int{} { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = n } - for range s { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + for range s { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - switch a { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + switch a { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` case "a": } - switch b.(type) { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + switch b.(type) { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` case int: } - f := func() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + f := func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - f2 := Call(func() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + f2 := Call(func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 }) @@ -109,16 +109,16 @@ func fn8(a string, b any, s []string) { // want `unnecessary whitespace decrease func fn9() { switch { - case 1: // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + case 1: // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 - case 2: - // This is a comment // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + case 2: // want +2 `unnecessary whitespace decreases readability \(leading-whitespace\)` + // This is a comment _ = 2 - default: // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + default: // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 3 diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go.golden b/testdata/src/default_config/whitespace/leading_whitespace.go.golden index 45d79ed..c57e2ef 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go.golden +++ b/testdata/src/default_config/whitespace/leading_whitespace.go.golden @@ -6,17 +6,17 @@ func Call(fn func()) func() { return fn } -func fn1() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn1() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` fmt.Println("Hello, World") } -func fn2() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn2() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` // Space before comment. fmt.Println("Hello, World") } -func fn3() { - // Space after comment // want `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn3() { // want +2 `unnecessary whitespace decreases readability \(leading-whitespace\)` + // Space after comment fmt.Println("Hello, World") } @@ -37,7 +37,7 @@ func fn51() { } func fn6() { - if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } } @@ -45,45 +45,45 @@ func fn6() { func fn7() { if true { _ = 1 - } else if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 - } else { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } } -func fn8(a string, b any, s []string) { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` - if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn8(a string, b any, s []string) { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 - } else if true { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - for i := 0; i < 1; i++ { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + for i := 0; i < 1; i++ { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - for n := range []int{} { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + for n := range []int{} { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = n } - for range s { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + for range s { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - switch a { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + switch a { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` case "a": } - switch b.(type) { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + switch b.(type) { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` case int: } - f := func() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + f := func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 } - f2 := Call(func() { // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + f2 := Call(func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 }) @@ -93,14 +93,14 @@ func fn8(a string, b any, s []string) { // want `unnecessary whitespace decrease func fn9() { switch { - case 1: // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + case 1: // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 1 - case 2: - // This is a comment // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + case 2: // want +2 `unnecessary whitespace decreases readability \(leading-whitespace\)` + // This is a comment _ = 2 - default: // want `unnecessary whitespace decreases readability \(leading-whitespace\)` + default: // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` _ = 3 } diff --git a/testdata/src/default_config/whitespace/trailing_whitespace.go b/testdata/src/default_config/whitespace/trailing_whitespace.go index ca6fdc4..1b0af75 100644 --- a/testdata/src/default_config/whitespace/trailing_whitespace.go +++ b/testdata/src/default_config/whitespace/trailing_whitespace.go @@ -7,15 +7,15 @@ func Call(fn func()) func() { } func fn1() { - fmt.Println("Hello, World") + fmt.Println("Hello, World") // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` -} // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` +} func fn2() { - fmt.Println("Hello, World") + fmt.Println("Hello, World") // want +2 `unnecessary whitespace decreases readability \(trailing-whitespace\)` // Comment with wihtespace -} // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` +} func fn3() { fmt.Println("Hello, World") @@ -31,47 +31,47 @@ func fn4() { func fn5() { if true { - _ = 1 + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } } func fn6() { if true { - _ = 1 + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` - } else if true { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` - _ = 1 + } else if true { + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` - } else { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` - _ = 1 + } else { + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } } func fn8(a string, b any, s []string) { if true { - _ = 1 + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` - } else if true { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` - _ = 1 + } else if true { + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } for i := 0; i < 1; i++ { - _ = 1 + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } for n := range []int{} { - _ = n + _ = n // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } for range s { - _ = 1 + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } switch a { case "a": @@ -84,14 +84,14 @@ func fn8(a string, b any, s []string) { } f := func() { - _ = 1 + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } f2 := Call(func() { - _ = 1 + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` - }) // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + }) _ = f _ = f2 diff --git a/testdata/src/default_config/whitespace/trailing_whitespace.go.golden b/testdata/src/default_config/whitespace/trailing_whitespace.go.golden index 13b7429..50cb73c 100644 --- a/testdata/src/default_config/whitespace/trailing_whitespace.go.golden +++ b/testdata/src/default_config/whitespace/trailing_whitespace.go.golden @@ -7,13 +7,13 @@ func Call(fn func()) func() { } func fn1() { - fmt.Println("Hello, World") -} // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + fmt.Println("Hello, World") // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` +} func fn2() { - fmt.Println("Hello, World") + fmt.Println("Hello, World") // want +2 `unnecessary whitespace decreases readability \(trailing-whitespace\)` // Comment with wihtespace -} // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` +} func fn3() { fmt.Println("Hello, World") @@ -29,38 +29,38 @@ func fn4() { func fn5() { if true { - _ = 1 - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } } func fn6() { if true { - _ = 1 - } else if true { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` - _ = 1 - } else { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` - _ = 1 - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } else if true { + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } else { + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } } func fn8(a string, b any, s []string) { if true { - _ = 1 - } else if true { // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` - _ = 1 - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } else if true { + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } for i := 0; i < 1; i++ { - _ = 1 - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } for n := range []int{} { - _ = n - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = n // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } for range s { - _ = 1 - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } switch a { case "a": @@ -73,14 +73,13 @@ func fn8(a string, b any, s []string) { } f := func() { - _ = 1 - } // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + } f2 := Call(func() { - _ = 1 - }) // want `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + }) _ = f _ = f2 } - diff --git a/wsl.go b/wsl.go index 5bc1697..295083d 100644 --- a/wsl.go +++ b/wsl.go @@ -830,7 +830,7 @@ func (w *WSL) CheckLeadingNewline(startPos token.Pos, body []ast.Stmt, comments firstStmtLine := w.Fset.PositionFor(firstStmt, false).Line if openingPosLine != firstStmtLine-1 { - w.addError(openingPos, openingPos, firstStmt, MessageRemoveWhitespace, CheckLeadingWhitespace) + w.addError(openingPos+1, openingPos, firstStmt, MessageRemoveWhitespace, CheckLeadingWhitespace) } } @@ -875,7 +875,7 @@ func (w *WSL) CheckTrailingNewline(body *ast.BlockStmt) { lastStmtLine := w.Fset.PositionFor(lastStmtOrComment, false).Line if closingPosLine != lastStmtLine+1 { - w.addError(closingPos, lastStmtOrComment, closingPos, MessageRemoveWhitespace, CheckTrailingWhitespace) + w.addError(lastStmtOrComment+1, lastStmtOrComment, closingPos, MessageRemoveWhitespace, CheckTrailingWhitespace) } } From b9b7fed0f91fcf7eec9f7d14a0833c0ec3586a2e Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 5 May 2025 21:37:36 +0200 Subject: [PATCH 073/112] Ensure we never remove user comments --- .../with_config/if_errcheck/if_errcheck.go | 40 ++++++++++++++++++- .../if_errcheck/if_errcheck.go.golden | 39 +++++++++++++++++- wsl.go | 39 +++++++++++++++++- 3 files changed, 112 insertions(+), 6 deletions(-) diff --git a/testdata/src/with_config/if_errcheck/if_errcheck.go b/testdata/src/with_config/if_errcheck/if_errcheck.go index 030c8be..aba93e2 100644 --- a/testdata/src/with_config/if_errcheck/if_errcheck.go +++ b/testdata/src/with_config/if_errcheck/if_errcheck.go @@ -3,18 +3,54 @@ package testpkg import "errors" func fn1() { + err := errors.New("x") // want +1 `unnecessary whitespace decreases readability \(err\)` + + if err != nil { + panic(err) + } +} + +func fn11() { // want +2 `unnecessary whitespace decreases readability \(err\)` err := errors.New("x") - if err != nil { // want `unnecessary whitespace decreases readability \(err\)` + if err != nil { + panic(err) + } +} + +func fn12() { + err := errors.New("x") + // Some comment + if err != nil { + panic(err) + } +} + +func fn13() { + err := errors.New("x") + + // Some comment + if err != nil { panic(err) } } func fn2() { a := 1 + err := errors.New("x") // want +1 `unnecessary whitespace decreases readability \(err\)` + + if err != nil { + panic(err) + } + + _ = a +} + +func fn21() { + a := 1 // want +2 `unnecessary whitespace decreases readability \(err\)` err := errors.New("x") - if err != nil { // want `unnecessary whitespace decreases readability \(err\)` + if err != nil { panic(err) } diff --git a/testdata/src/with_config/if_errcheck/if_errcheck.go.golden b/testdata/src/with_config/if_errcheck/if_errcheck.go.golden index 10ee2fa..166902c 100644 --- a/testdata/src/with_config/if_errcheck/if_errcheck.go.golden +++ b/testdata/src/with_config/if_errcheck/if_errcheck.go.golden @@ -4,8 +4,32 @@ package testpkg import "errors" func fn1() { + err := errors.New("x") // want +1 `unnecessary whitespace decreases readability \(err\)` + if err != nil { + panic(err) + } +} + +func fn11() { // want +2 `unnecessary whitespace decreases readability \(err\)` + err := errors.New("x") + if err != nil { + panic(err) + } +} + +func fn12() { + err := errors.New("x") + // Some comment + if err != nil { + panic(err) + } +} + +func fn13() { err := errors.New("x") - if err != nil { // want `unnecessary whitespace decreases readability \(err\)` + + // Some comment + if err != nil { panic(err) } } @@ -13,8 +37,19 @@ func fn1() { func fn2() { a := 1 + err := errors.New("x") // want +1 `unnecessary whitespace decreases readability \(err\)` + if err != nil { + panic(err) + } + + _ = a +} + +func fn21() { + a := 1 // want +2 `unnecessary whitespace decreases readability \(err\)` + err := errors.New("x") - if err != nil { // want `unnecessary whitespace decreases readability \(err\)` + if err != nil { panic(err) } diff --git a/wsl.go b/wsl.go index 295083d..aa9a30c 100644 --- a/wsl.go +++ b/wsl.go @@ -239,7 +239,39 @@ func (w *WSL) checkError( return } - w.addError(ifStmt.Pos(), previousNode.End(), ifStmt.Pos(), MessageRemoveWhitespace, cursor.checkType) + previousNodeEnd := previousNode.End() + + comments := ast.NewCommentMap(w.Fset, previousNode, w.File.Comments) + for _, cg := range comments { + for _, c := range cg { + if c.Pos() < previousNodeEnd || c.End() > ifStmt.Pos() { + continue + } + + if c.End() > previousNodeEnd { + // There's a comment between the error variable and the + // if-statement, we can't do much about this. Most likely, the + // comment has a meaning, but even if not we would end up with + // something like + // + // err := fn() + // // Some Comment + // if err != nil {} + // + // Which just feels marginally better than leaving the space + // anyway. + if w.lineFor(c.End()) != w.lineFor(previousNodeEnd) { + return + } + + // If they are on the same line though, we can just extend where + // the line ends. + previousNodeEnd = c.End() + } + } + } + + w.addError(previousNodeEnd+1, previousNodeEnd, ifStmt.Pos(), MessageRemoveWhitespace, cursor.checkType) // If we add the error at the same position but with a different fix // range, only the fix range will be updated. @@ -257,8 +289,11 @@ func (w *WSL) checkError( // if err != nil {} cursor.Previous() + // We report this fix on the same pos as the previous diagnostic, but the + // fix is different. The reason is to just stack more fixes for the same + // diagnostic, the issue isn't present until the first fix. if w.numberOfStatementsAbove(cursor) > 0 { - w.addError(ifStmt.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace, cursor.checkType) + w.addError(previousNodeEnd+1, previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace, cursor.checkType) } } From 3645b0b3bdbd9635f8867b5c6b0c8f93c9bf43e4 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 5 May 2025 21:39:52 +0200 Subject: [PATCH 074/112] Don't report whitespace issues on the same line --- testdata/src/default_config/whitespace/leading_whitespace.go | 2 ++ .../default_config/whitespace/leading_whitespace.go.golden | 2 ++ wsl.go | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go b/testdata/src/default_config/whitespace/leading_whitespace.go index bf6f0ad..0336071 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go +++ b/testdata/src/default_config/whitespace/leading_whitespace.go @@ -6,6 +6,8 @@ func Call(fn func()) func() { return fn } +func fn0() { fmt.Println("Hello, World") } + func fn1() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` fmt.Println("Hello, World") diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go.golden b/testdata/src/default_config/whitespace/leading_whitespace.go.golden index c57e2ef..7cc7cdf 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go.golden +++ b/testdata/src/default_config/whitespace/leading_whitespace.go.golden @@ -6,6 +6,8 @@ func Call(fn func()) func() { return fn } +func fn0() { fmt.Println("Hello, World") } + func fn1() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` fmt.Println("Hello, World") } diff --git a/wsl.go b/wsl.go index aa9a30c..0e1692c 100644 --- a/wsl.go +++ b/wsl.go @@ -864,7 +864,7 @@ func (w *WSL) CheckLeadingNewline(startPos token.Pos, body []ast.Stmt, comments openingPosLine := w.Fset.PositionFor(openingPos, false).Line firstStmtLine := w.Fset.PositionFor(firstStmt, false).Line - if openingPosLine != firstStmtLine-1 { + if firstStmtLine > openingPosLine+1 { w.addError(openingPos+1, openingPos, firstStmt, MessageRemoveWhitespace, CheckLeadingWhitespace) } } @@ -909,7 +909,7 @@ func (w *WSL) CheckTrailingNewline(body *ast.BlockStmt) { closingPosLine := w.Fset.PositionFor(closingPos, false).Line lastStmtLine := w.Fset.PositionFor(lastStmtOrComment, false).Line - if closingPosLine != lastStmtLine+1 { + if closingPosLine > lastStmtLine+1 { w.addError(lastStmtOrComment+1, lastStmtOrComment, closingPos, MessageRemoveWhitespace, CheckTrailingWhitespace) } } From 9e0ac25dc5aaa405107415e3f315c89df1a7cf03 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 5 May 2025 21:55:13 +0200 Subject: [PATCH 075/112] Doc updates --- README.md | 4 ++++ RULES.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 54beea8..f0409c1 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ This is an exhaustive list of all the checks that can be enabled or disabled and their default value. The names are the same as the Go [AST](https://pkg.go.dev/go/ast) type name for built-ins. +The base rule is that statements that has a block (e.g. `for`, `range`, +`switch`, `if` etc) should always only be directly adjacent with a single +variable and only if it's used in the expression in the block itself. + For more details and examples, see [RULES](RULES.md). ✅ = enabled by default, ❌ = disabled by default diff --git a/RULES.md b/RULES.md index 1782bf2..b5ca7b8 100644 --- a/RULES.md +++ b/RULES.md @@ -403,7 +403,7 @@ if b > 1 { a := 1 b := 2 if a > 1 { - fmt.Println("a > b") + fmt.Println("a > 1") } a := 1 @@ -558,7 +558,7 @@ See [`for`](#for), same rules apply but for the keyword `range`. ```go someRange := []int{1, 2, 3} -for _, i := range anotherRange { +for _, i := range thisIsNotSomeRange { fmt.Println(i) } @@ -580,7 +580,7 @@ for _, v := range s2 { ```go someRange := []int{1, 2, 3} -for _, i := range anotherRange { +for _, i := range thisIsNotSomeRange { fmt.Println(i) } @@ -619,10 +619,60 @@ Only variables used in the `range` are cuddled and at most one statement above t ## `return` -See [`break`](#break), same rules apply but for the keyword `return`. - > Configurable via `branch-max-lines` +Return statements is an important statement that is easiy to miss in larger code +blocks. To better visualize the `return` statement and that the method is +returning it should always be followed by a blank line unless the scope is as +small as `branch-max-lines`. + + + + + + + +
BadGood
+ +```go +func Fn() int { + x, err := someFn() + if err != nil { + panic(err) + } + + fmt.Println(x) + return +} +``` + + + +```go +func Fn() int { + x, err := someFn() + if err != nil { + panic(err) + } + + fmt.Println(x) + + return +} +``` + +
+ +The block is big enough to warrant an explicit return which is otherwise easy to +miss. + + + +The return statement is isolated on it's own line, making it stand out a bit +more. + +
+ ## `select` See [`for`](#for), same rules apply but for the keyword `range`. From 79c1072a197e34899288dad26e37dfe9c4fa839b Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 5 May 2025 22:03:02 +0200 Subject: [PATCH 076/112] Bump version --- .github/workflows/go.yml | 7 +++---- .golangci.yml | 2 ++ cmd/golangci-lint-migrate/main.go | 4 ++-- cmd/wsl/main.go | 2 +- config.go | 5 ++++- go.mod | 5 ++--- go.sum | 2 -- 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f43652c..89bbe28 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -3,7 +3,7 @@ name: CI on: push: branches: - - master + - main pull_request: jobs: @@ -15,12 +15,11 @@ jobs: - uses: actions/setup-go@v5 with: go-version: stable - - uses: golangci/golangci-lint-action@v7 + - uses: golangci/golangci-lint-action@v8 with: - version: v2.0.2 + version: v2.1.6 tests: - # run after golangci-lint action to not produce duplicated errors name: tests needs: golangci-lint strategy: diff --git a/.golangci.yml b/.golangci.yml index 8e7f879..0cb46b1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,6 +17,7 @@ linters: - err113 - exhaustruct - forbidigo + - funcorder - funlen - gocognit - gocyclo @@ -30,6 +31,7 @@ linters: - paralleltest - prealloc - rowserrcheck + - tagliatelle - testpackage - tparallel - varnamelen diff --git a/cmd/golangci-lint-migrate/main.go b/cmd/golangci-lint-migrate/main.go index 069cfef..4b55ba9 100644 --- a/cmd/golangci-lint-migrate/main.go +++ b/cmd/golangci-lint-migrate/main.go @@ -7,7 +7,7 @@ import ( "os" "slices" - "github.com/bombsimon/wsl/v4" + "github.com/bombsimon/wsl/v5" "gopkg.in/yaml.v3" ) @@ -52,7 +52,7 @@ type WSL struct { ForceCaseTrailingWhitespace int `yaml:"force-case-trailing-whitespace"` AllowCuddlingWithCalls []string `yaml:"allow-cuddle-with-calls"` - AllowCuddleWithRhs []string `yaml:"allow-cuddle-with-rhs"` + AllowCuddleWithRHS []string `yaml:"allow-cuddle-with-rhs"` ErrorVariableNames []string `yaml:"error-variable-names"` } diff --git a/cmd/wsl/main.go b/cmd/wsl/main.go index 92d1e8a..0139ea0 100644 --- a/cmd/wsl/main.go +++ b/cmd/wsl/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/bombsimon/wsl/v4" + "github.com/bombsimon/wsl/v5" "golang.org/x/tools/go/analysis/singlechecker" ) diff --git a/config.go b/config.go index c1e77ee..dc4cd23 100644 --- a/config.go +++ b/config.go @@ -41,12 +41,14 @@ const ( // // a = 1 // b = 2 + // . CheckAssignExclusive // Check append only allows assignments of `append` to be cuddled with other // assignments if it's a variable used in the append statement, e.g. // // a := 1 // x = append(x, a) + // . CheckAppend // Force error checking to follow immediately after an error variable is // assigned, e.g. @@ -55,11 +57,12 @@ const ( // if err != nil { // panic(err) // } + // . CheckErr CheckLeadingWhitespace CheckTrailingWhitespace - // CheckTypes only used for reporting + // CheckTypes only used for reporting. CheckCaseTrailingNewline ) diff --git a/go.mod b/go.mod index bd38ca0..69ce1ec 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ -module github.com/bombsimon/wsl/v4 +module github.com/bombsimon/wsl/v5 go 1.24 require ( github.com/stretchr/testify v1.9.0 golang.org/x/tools v0.32.0 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -13,5 +13,4 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/sync v0.13.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e9d6a2e..b2e79ca 100644 --- a/go.sum +++ b/go.sum @@ -14,7 +14,5 @@ golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From e0a93ae210acba77150b88d6e1d9c977f1506886 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 5 May 2025 22:08:47 +0200 Subject: [PATCH 077/112] Update LICENSE year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 4bfd049..f881b64 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 - 2023 Simon Sawert +Copyright (c) 2018 - 2025 Simon Sawert Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 225c0c71c7a43bcc7615e5c55dffc4d5e51b8f40 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 5 May 2025 22:10:54 +0200 Subject: [PATCH 078/112] Rename faulty struct names --- cmd/golangci-lint-migrate/main.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/golangci-lint-migrate/main.go b/cmd/golangci-lint-migrate/main.go index 4b55ba9..9bc7685 100644 --- a/cmd/golangci-lint-migrate/main.go +++ b/cmd/golangci-lint-migrate/main.go @@ -15,11 +15,11 @@ type Config struct { Version string `yaml:"version"` } -type V1ToV4 struct { +type V1 struct { Settings Settings `yaml:"linters-settings"` } -type V5 struct { +type V2 struct { Linters Linters `yaml:"linters"` } @@ -162,14 +162,14 @@ func getWslConfig(filename string) *WSL { switch cfg.Version { case "": - var cfg V1ToV4 + var cfg V1 if err := yaml.Unmarshal(yamlFile, &cfg); err != nil { log.Fatalf("%v", err) } return cfg.Settings.WSL case "2": - var cfg V5 + var cfg V2 if err := yaml.Unmarshal(yamlFile, &cfg); err != nil { log.Fatalf("%v", err) } From 9cff0d60ec90cce5d82cca9cfd578437007efd8c Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 5 May 2025 22:46:26 +0200 Subject: [PATCH 079/112] Test updates --- testdata/src/default_config/expr/expr.go | 5 +++++ .../src/default_config/expr/expr.go.golden | 6 ++++++ .../src/with_config/disable_all/disable.go | 3 --- testdata/src/with_config/send/send.go | 20 ++++++++++-------- testdata/src/with_config/send/send.go.golden | 21 ++++++++++--------- 5 files changed, 33 insertions(+), 22 deletions(-) diff --git a/testdata/src/default_config/expr/expr.go b/testdata/src/default_config/expr/expr.go index b1119c5..c91fc6a 100644 --- a/testdata/src/default_config/expr/expr.go +++ b/testdata/src/default_config/expr/expr.go @@ -39,3 +39,8 @@ func fn3() { _ = c _ = d } + +func fn4() { + b := 2 + <-ch // want `missing whitespace decreases readability \(expr\)` +} diff --git a/testdata/src/default_config/expr/expr.go.golden b/testdata/src/default_config/expr/expr.go.golden index 36473ea..e8cc223 100644 --- a/testdata/src/default_config/expr/expr.go.golden +++ b/testdata/src/default_config/expr/expr.go.golden @@ -42,3 +42,9 @@ func fn3() { _ = c _ = d } + +func fn4() { + b := 2 + + <-ch // want `missing whitespace decreases readability \(expr\)` +} diff --git a/testdata/src/with_config/disable_all/disable.go b/testdata/src/with_config/disable_all/disable.go index 9e94872..a467eb3 100644 --- a/testdata/src/with_config/disable_all/disable.go +++ b/testdata/src/with_config/disable_all/disable.go @@ -138,9 +138,6 @@ func select(ctx context.Context, ch1 chan struct{}) { func send(ch chan int) { a := 1 ch <- 1 - - b := 2 - <-ch } func switch() { diff --git a/testdata/src/with_config/send/send.go b/testdata/src/with_config/send/send.go index 731f6ec..d4d4ce8 100644 --- a/testdata/src/with_config/send/send.go +++ b/testdata/src/with_config/send/send.go @@ -1,17 +1,19 @@ package testpkg func fn1(ch chan int) { - a := 1 - ch <- 1 // want `missing whitespace decreases readability \(send\)` - - b := 2 - <-ch // want `missing whitespace decreases readability \(expr\)` -} - -func fn2(ch chan int) { a := 1 ch <- a b := 1 - b = <-ch + ch <- someFn(b) + + c := 1 + ch <- 1 // want `missing whitespace decreases readability \(send\)` + + // TODO: Technically this is used first in block but there's no easy way to + // figure out the first statement in the block so for now this is not valid. + d := 2 + ch <- func() int { // want `missing whitespace decreases readability \(send\)` + return d + }() } diff --git a/testdata/src/with_config/send/send.go.golden b/testdata/src/with_config/send/send.go.golden index b6fb302..d4f93b5 100644 --- a/testdata/src/with_config/send/send.go.golden +++ b/testdata/src/with_config/send/send.go.golden @@ -2,19 +2,20 @@ package testpkg func fn1(ch chan int) { a := 1 + ch <- a - ch <- 1 // want `missing whitespace decreases readability \(send\)` + b := 1 + ch <- someFn(b) - b := 2 + c := 1 - <-ch // want `missing whitespace decreases readability \(expr\)` -} + ch <- 1 // want `missing whitespace decreases readability \(send\)` -func fn2(ch chan int) { - a := 1 - ch <- a + // TODO: Technically this is used first in block but there's no easy way to + // figure out the first statement in the block so for now this is not valid. + d := 2 - b := 1 - b = <-ch + ch <- func() int { // want `missing whitespace decreases readability \(send\)` + return d + }() } - From e477d6042d911bfff639857fc8b90c82fffac01c Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 5 May 2025 23:08:16 +0200 Subject: [PATCH 080/112] Doc updates --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f0409c1..f744691 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ in more details. go install github.com/bombsimon/wsl/v5/cmd/wsl@latest # Main branch -go install github.com/bombsimon/wsl/v5/cmd/wsl@master +go install github.com/bombsimon/wsl/v5/cmd/wsl@main ``` ## Usage @@ -110,7 +110,7 @@ the default analysis flags and way of working. wsl --help wsl [flags] -wsl --disable-all --enable break,continue --fix ./... +wsl --default none --enable branch,return --fix ./... ``` `wsl` is also integrated in [`golangci-lint`][golangci-lint] @@ -161,7 +161,7 @@ linters: ## See also -- [`nlterutn`][nlreturn] - Use empty lines before `return` +- [`nlreturn`][nlreturn] - Use empty lines before `return` - [`whitespace`][whitespace] - Don't use a blank newline at the start or end of a block. - [`gofumpt`][gofumpt] - Stricter formatter than `gofmt`. From f7b43e9a29e16cdfb5d90056c177795f84a91c7d Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 6 May 2025 21:37:23 +0200 Subject: [PATCH 081/112] Remove tests --- testdata/src/default_config/v1/v1.go | 12 ------------ testdata/src/default_config/v1/v1.go.golden | 13 ------------- 2 files changed, 25 deletions(-) delete mode 100644 testdata/src/default_config/v1/v1.go delete mode 100644 testdata/src/default_config/v1/v1.go.golden diff --git a/testdata/src/default_config/v1/v1.go b/testdata/src/default_config/v1/v1.go deleted file mode 100644 index 7d4b57f..0000000 --- a/testdata/src/default_config/v1/v1.go +++ /dev/null @@ -1,12 +0,0 @@ -package testdata - -func fn1(s map[string]bool) { - // TODO: Regression v1 - timeout := 10 - switch { // want `missing whitespace decreases readability` - case s["bad timeout"]: - timeout = 100 - case s["zero timeout"]: - timeout = 0 - } -} diff --git a/testdata/src/default_config/v1/v1.go.golden b/testdata/src/default_config/v1/v1.go.golden deleted file mode 100644 index bb76cac..0000000 --- a/testdata/src/default_config/v1/v1.go.golden +++ /dev/null @@ -1,13 +0,0 @@ -package testdata - -func fn1(s map[string]bool) { - // TODO: Regression v1 - timeoutx := 10 - - switch { // want `missing whitespace decreases readability` - case s["bad timeout"]: - timeout = 100 - case s["zero timeout"]: - timeout = 0 - } -} From 62649f4a2876f63e6908eba01815b58627368f4f Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 6 May 2025 21:47:36 +0200 Subject: [PATCH 082/112] Refactor cuddling check with intersection --- wsl.go | 76 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/wsl.go b/wsl.go index 0e1692c..dbe1d91 100644 --- a/wsl.go +++ b/wsl.go @@ -74,21 +74,18 @@ func (w *WSL) checkCuddlingWithDecl( cursor *Cursor, maxAllowedStatements int, ) { - defer cursor.Save()() - - currentIdents := identsFromNode(cursor.Stmt(), true) - previousIdents := []*ast.Ident{} - var previousNode ast.Node + resetCursor := cursor.Save() + if cursor.Previous() { previousNode = cursor.Stmt() - previousIdents = identsFromNode(previousNode, true) - - cursor.Next() // Move forward again } + resetCursor() + numStmtsAbove := w.numberOfStatementsAbove(cursor) + previousIdents := identsFromNode(previousNode, true) // If we don't have any statements above, we only care about potential error // cuddling (for if statements) so check that. @@ -97,29 +94,43 @@ func (w *WSL) checkCuddlingWithDecl( return } - _, prevIsAssign := previousNode.(*ast.AssignStmt) - _, prevIsDecl := previousNode.(*ast.DeclStmt) - _, prevIsIncDec := previousNode.(*ast.IncDecStmt) + nodeIsAssignDeclOrIncDec := func(n ast.Node) bool { + _, a := n.(*ast.AssignStmt) + _, d := n.(*ast.DeclStmt) + _, i := n.(*ast.IncDecStmt) + + return a || d || i + } + _, currIsDefer := stmt.(*ast.DeferStmt) // We're cuddled but not with an assign, declare or defer statement which is // never allowed. - if !prevIsAssign && !prevIsDecl && !currIsDefer && !prevIsIncDec { + if !nodeIsAssignDeclOrIncDec(previousNode) && !currIsDefer { w.addError(cursor.Stmt().Pos(), cursor.Stmt().Pos(), cursor.Stmt().Pos(), MessageAddWhitespace, cursor.checkType) return } - // FEATURE(AllowWholeBlock): Allow identifier used anywhere in block (including recursive blocks). - if w.Config.AllowWholeBlock { - allIdentsInBlock := identsFromNode(stmt, false) - - anyIntersects := identIntersection(previousIdents, allIdentsInBlock) + checkIntersection := func(other []*ast.Ident) bool { + anyIntersects := identIntersection(previousIdents, other) if len(anyIntersects) > 0 { // We have matches, but too many statements above. if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace, cursor.checkType) } + return true + } + + return false + } + + // FEATURE(AllowWholeBlock): Allow identifier used anywhere in block + // (including recursive blocks). + if w.Config.AllowWholeBlock { + allIdentsInBlock := identsFromNode(stmt, false) + + if checkIntersection(allIdentsInBlock) { return } } @@ -128,35 +139,24 @@ func (w *WSL) checkCuddlingWithDecl( if !w.Config.AllowWholeBlock && w.Config.AllowFirstInBlock { firstStmtIdents := identsFromNode(firstBlockStmt, true) - anyIntersects := identIntersection(previousIdents, firstStmtIdents) - if len(anyIntersects) > 0 { - // We have matches, but too many statements above. - if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { - w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace, cursor.checkType) - } - + if checkIntersection(firstStmtIdents) { return } } - // We're cuddled but the line immediately above doesn't contain any - // variables used in this statement. - intersects := identIntersection(currentIdents, previousIdents) - if len(intersects) == 0 { - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) + currentIdents := identsFromNode(cursor.Stmt(), true) + if checkIntersection(currentIdents) { return } - // Idents on the line above exist in the current condition so that should - // remain cuddled. + intersects := identIntersection(currentIdents, previousIdents) if len(intersects) > 0 { - // Check the maximum number of allowed statements above, and if not - // disabled (-1) check that the previous one intersects with the current - // one. - if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { - w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace, cursor.checkType) - } + return } + + // We're cuddled but the line immediately above doesn't contain any + // variables used in this statement. + w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) } func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { @@ -219,6 +219,8 @@ func (w *WSL) checkError( previousIdents []*ast.Ident, cursor *Cursor, ) { + defer cursor.Save()() + if _, ok := ifStmt.(*ast.IfStmt); !ok { return } From fb7ee338a124ff43f236ec8c3bdd58cbc000408a Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 6 May 2025 22:07:42 +0200 Subject: [PATCH 083/112] Improve decl check --- testdata/src/default_config/decl/decl.go | 7 ++++ .../src/default_config/decl/decl.go.golden | 6 +++ wsl.go | 38 ++++++++++++++++--- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/testdata/src/default_config/decl/decl.go b/testdata/src/default_config/decl/decl.go index 1ee3403..379e820 100644 --- a/testdata/src/default_config/decl/decl.go +++ b/testdata/src/default_config/decl/decl.go @@ -21,3 +21,10 @@ func fn3() { a := 1 var b = a // want `missing whitespace decreases readability \(decl\)` } + +func fn4() { + var x = func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + + return 1 + }() +} diff --git a/testdata/src/default_config/decl/decl.go.golden b/testdata/src/default_config/decl/decl.go.golden index e1e1bef..192ee79 100644 --- a/testdata/src/default_config/decl/decl.go.golden +++ b/testdata/src/default_config/decl/decl.go.golden @@ -27,3 +27,9 @@ func fn3() { var b = a // want `missing whitespace decreases readability \(decl\)` } + +func fn4() { + var x = func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + return 1 + }() +} diff --git a/wsl.go b/wsl.go index dbe1d91..845d82c 100644 --- a/wsl.go +++ b/wsl.go @@ -478,16 +478,15 @@ func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) } -func (w *WSL) CheckDecl(stmt *ast.DeclStmt, cursor *Cursor) { +func (w *WSL) CheckDeclStmt(stmt *ast.DeclStmt, cursor *Cursor) { + w.CheckDecl(stmt.Decl, cursor) + if _, ok := w.Config.Checks[CheckDecl]; !ok { return } cursor.SetChecker(CheckDecl) - // TODO: Decl might be a block that needs analyzing, e.g. - // var x = func() {} - if w.numberOfStatementsAbove(cursor) == 0 { return } @@ -705,7 +704,7 @@ func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { w.CheckBranch(s, cursor) // var a case *ast.DeclStmt: - w.CheckDecl(s, cursor) + w.CheckDeclStmt(s, cursor) // a := a case *ast.AssignStmt: w.CheckAssign(s, cursor) @@ -772,12 +771,39 @@ func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) { *ast.SelectorExpr, *ast.SliceExpr, *ast.TypeAssertExpr, - *ast.UnaryExpr: + *ast.UnaryExpr, + nil: default: fmt.Printf("Not implemented expr: %T\n", s) } } +func (w *WSL) CheckDecl(decl ast.Decl, cursor *Cursor) { + switch d := decl.(type) { + case *ast.GenDecl: + for _, spec := range d.Specs { + w.CheckSpec(spec, cursor) + } + case *ast.FuncDecl: + w.CheckStmt(d.Body, cursor) + case *ast.BadDecl: + default: + fmt.Printf("Not implemented decl: %T\n", d) + } +} + +func (w *WSL) CheckSpec(spec ast.Spec, cursor *Cursor) { + switch s := spec.(type) { + case *ast.ValueSpec: + for _, expr := range s.Values { + w.CheckExpr(expr, cursor) + } + case *ast.ImportSpec, *ast.TypeSpec: + default: + fmt.Printf("Not implemented spec: %T\n", s) + } +} + // numberOfStatementsAbove will find out how many lines above the cursor's // current statement there is without any newlines between. func (w *WSL) numberOfStatementsAbove(cursor *Cursor) int { From f35c58c4b89a6f927ec5a3daeb5e50febd7ea4b9 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Tue, 6 May 2025 22:50:57 +0200 Subject: [PATCH 084/112] Branch and return fixes --- README.md | 13 ++++++------ .../with_config/branch_max_lines/branch.go | 18 +++++++++++++++++ .../branch_max_lines/branch.go.golden | 20 +++++++++++++++++++ .../case.go | 1 - .../case.go.golden | 1 - wsl.go | 4 ++-- wsl_test.go | 10 ++++++++-- 7 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 testdata/src/with_config/branch_max_lines/branch.go create mode 100644 testdata/src/with_config/branch_max_lines/branch.go.golden rename testdata/src/with_config/{case_trailing_whitespace => case_max_lines}/case.go (98%) rename testdata/src/with_config/{case_trailing_whitespace => case_max_lines}/case.go.golden (98%) diff --git a/README.md b/README.md index f744691..0e4231c 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ For more details and examples, see [RULES](RULES.md). declarations or increment/decrement - ✅ **branch** - Branch statement (`break`, `continue`, `fallthrough`, `goto`) should only be cuddled if the block is less than `n` lines where `n` is the - value of [`branch-max-statements`](#configuration) + value of [`branch-max-lines`](#configuration) - ✅ **decl** - Declarations should never be cuddled - ✅ **defer** - Defer should only be cuddled with other `defer`, after error checking or with variable used on the line above @@ -50,7 +50,7 @@ For more details and examples, see [RULES](RULES.md). - ✅ **range** - Range should only be cuddled with a single variable used on the line above - ✅ **return** - Return should only be cuddled if the block is less than `n` - lines where `n` is the value of [`branch-max-statements`](#configuration) + lines where `n` is the value of [`branch-max-lines`](#configuration) - ✅ **select** - Select should only be cuddled with a single variable used on the line above - ❌ **send** - Send should only be cuddled with a single variable used on the line @@ -81,11 +81,12 @@ in more details. the variable - ❌ **allow-whole-block** - Same as above, but allows cuddling if the variable is used _anywhere_ in the following (or nested) block -- **branch-max-lines** - If a block contains less than this number of lines the - branch statement (e.g. `return`, `break`, `continue`) does not need to be - separated by a whitespace (default 2, 0 = off) +- **branch-max-lines** - If a block contains more than this number of lines the + branch statement (e.g. `return`, `break`, `continue`) need to be separated by + a whitespace (default 2) - **case-max-lines** - If set to a non negative number, `case` blocks needs to - end with a whitespace if exceeding this number (default 0, 0 = off) + end with a whitespace if exceeding this number (default 0, 0 = off, 1 = + always) - ❌ **include-generated** - Include generated files when checking ## Installation diff --git a/testdata/src/with_config/branch_max_lines/branch.go b/testdata/src/with_config/branch_max_lines/branch.go new file mode 100644 index 0000000..68653b9 --- /dev/null +++ b/testdata/src/with_config/branch_max_lines/branch.go @@ -0,0 +1,18 @@ +package testpkg + +func fn1() int { + _ = 1 + _ = 2 + _ = 3 + _ = 4 + return 1 +} + +func fn2() int { + _ = 1 + _ = 2 + _ = 3 + _ = 4 + _ = 5 + return 1 // want `missing whitespace decreases readability \(return\)` +} diff --git a/testdata/src/with_config/branch_max_lines/branch.go.golden b/testdata/src/with_config/branch_max_lines/branch.go.golden new file mode 100644 index 0000000..6524ba3 --- /dev/null +++ b/testdata/src/with_config/branch_max_lines/branch.go.golden @@ -0,0 +1,20 @@ +package testpkg + +func fn1() int { + _ = 1 + _ = 2 + _ = 3 + _ = 4 + return 1 +} + +func fn2() int { + _ = 1 + _ = 2 + _ = 3 + _ = 4 + _ = 5 + + return 1 // want `missing whitespace decreases readability \(return\)` +} + diff --git a/testdata/src/with_config/case_trailing_whitespace/case.go b/testdata/src/with_config/case_max_lines/case.go similarity index 98% rename from testdata/src/with_config/case_trailing_whitespace/case.go rename to testdata/src/with_config/case_max_lines/case.go index 21e6520..0a9dd6b 100644 --- a/testdata/src/with_config/case_trailing_whitespace/case.go +++ b/testdata/src/with_config/case_max_lines/case.go @@ -30,7 +30,6 @@ func fn1(n int) { func fn2(ctx context.Context, ch1 chan struct{}) { select { case ctx.Done(): - _ = 1 _ = 1 _ = 1 _ = 1 // want `missing whitespace decreases readability \(case-trailing-newline\)` diff --git a/testdata/src/with_config/case_trailing_whitespace/case.go.golden b/testdata/src/with_config/case_max_lines/case.go.golden similarity index 98% rename from testdata/src/with_config/case_trailing_whitespace/case.go.golden rename to testdata/src/with_config/case_max_lines/case.go.golden index 394947f..dcedd88 100644 --- a/testdata/src/with_config/case_trailing_whitespace/case.go.golden +++ b/testdata/src/with_config/case_max_lines/case.go.golden @@ -31,7 +31,6 @@ func fn1(n int) { func fn2(ctx context.Context, ch1 chan struct{}) { select { case ctx.Done(): - _ = 1 _ = 1 _ = 1 _ = 1 // want `missing whitespace decreases readability \(case-trailing-newline\)` diff --git a/wsl.go b/wsl.go index 845d82c..74b6037 100644 --- a/wsl.go +++ b/wsl.go @@ -546,7 +546,7 @@ func (w *WSL) checkCaseTrailingNewline(body []ast.Stmt, cursor *Cursor) { lastStmt := body[len(body)-1] totalLines := w.lineFor(lastStmt.End()) - w.lineFor(firstStmt.Pos()) + 1 - if totalLines <= w.Config.CaseMaxLines { + if totalLines < w.Config.CaseMaxLines { return } @@ -580,7 +580,7 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { } // If the distance between the first statement and the return statement is - // less than 3 LOC we're allowed to cuddle. + // less than `n` LOC we're allowed to cuddle. firstStmts := cursor.Nth(0) if w.lineFor(stmt.End())-w.lineFor(firstStmts.Pos()) < w.Config.BranchMaxLines { return diff --git a/wsl_test.go b/wsl_test.go index 5e86394..b346385 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -56,9 +56,15 @@ func TestWithConfig(t *testing.T) { }, }, { - subdir: "case_trailing_whitespace", + subdir: "case_max_lines", configFn: func(config *Configuration) { - config.CaseMaxLines = 2 + config.CaseMaxLines = 3 + }, + }, + { + subdir: "branch_max_lines", + configFn: func(config *Configuration) { + config.BranchMaxLines = 5 }, }, { From f811cc2c6776109a75455e930b75a90a1b4a4a32 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 10 May 2025 14:45:49 +0200 Subject: [PATCH 085/112] More descriptive diagnostic --- testdata/src/default_config/assign/assign.go | 4 +- .../default_config/assign/assign.go.golden | 4 +- testdata/src/default_config/branch/branch.go | 8 +-- .../default_config/branch/branch.go.golden | 8 +-- testdata/src/default_config/decl/decl.go | 14 ++-- .../src/default_config/decl/decl.go.golden | 14 ++-- testdata/src/default_config/defer/defer.go | 8 +-- .../src/default_config/defer/defer.go.golden | 8 +-- testdata/src/default_config/expr/expr.go | 8 +-- .../src/default_config/expr/expr.go.golden | 8 +-- testdata/src/default_config/for/for.go | 6 +- testdata/src/default_config/for/for.go.golden | 6 +- testdata/src/default_config/go/go.go | 10 +-- testdata/src/default_config/go/go.go.golden | 10 +-- testdata/src/default_config/if/if.go | 6 +- testdata/src/default_config/if/if.go.golden | 6 +- testdata/src/default_config/label/label.go | 7 +- .../src/default_config/label/label.go.golden | 7 +- testdata/src/default_config/range/range.go | 6 +- .../src/default_config/range/range.go.golden | 6 +- testdata/src/default_config/return/return.go | 10 +-- .../default_config/return/return.go.golden | 10 +-- testdata/src/default_config/select/select.go | 2 +- .../default_config/select/select.go.golden | 2 +- testdata/src/default_config/switch/switch.go | 6 +- .../default_config/switch/switch.go.golden | 6 +- .../default_config/type_switch/type_switch.go | 4 +- .../type_switch/type_switch.go.golden | 4 +- .../whitespace/leading_whitespace.go | 38 +++++------ .../whitespace/leading_whitespace.go.golden | 38 +++++------ .../whitespace/trailing_whitespace.go | 26 ++++---- .../whitespace/trailing_whitespace.go.golden | 26 ++++---- .../with_config/branch_max_lines/branch.go | 2 +- .../branch_max_lines/branch.go.golden | 2 +- .../src/with_config/case_max_lines/case.go | 4 +- .../with_config/case_max_lines/case.go.golden | 4 +- .../with_config/exclusive_short_decl/code.go | 4 +- .../exclusive_short_decl/code.go.golden | 4 +- .../with_config/first_in_block_n1/block.go | 2 +- .../first_in_block_n1/block.go.golden | 2 +- .../with_config/if_errcheck/if_errcheck.go | 10 +-- .../if_errcheck/if_errcheck.go.golden | 10 +-- testdata/src/with_config/send/send.go | 4 +- testdata/src/with_config/send/send.go.golden | 4 +- testdata/src/with_config/whole_block/block.go | 12 ++-- .../with_config/whole_block/block.go.golden | 12 ++-- wsl.go | 66 ++++++++++++++----- 47 files changed, 250 insertions(+), 218 deletions(-) diff --git a/testdata/src/default_config/assign/assign.go b/testdata/src/default_config/assign/assign.go index bf9c4d6..6393c17 100644 --- a/testdata/src/default_config/assign/assign.go +++ b/testdata/src/default_config/assign/assign.go @@ -29,7 +29,7 @@ func strictAppend() { x := "c" s = append(s, x) y := "e" - s = append(s, "d") // want `missing whitespace decreases readability \(assign\)` + s = append(s, "d") // want `missing whitespace above this line \(no shared variables above assign\)` s = append(s, y) } @@ -62,5 +62,5 @@ func assignAfterBlock() { if x > 0 { return } - x = 2 // want `missing whitespace decreases readability \(assign\)` + x = 2 // want `missing whitespace above this line \(invalid statement above assign\)` } diff --git a/testdata/src/default_config/assign/assign.go.golden b/testdata/src/default_config/assign/assign.go.golden index 752228f..ef82370 100644 --- a/testdata/src/default_config/assign/assign.go.golden +++ b/testdata/src/default_config/assign/assign.go.golden @@ -30,7 +30,7 @@ func strictAppend() { s = append(s, x) y := "e" - s = append(s, "d") // want `missing whitespace decreases readability \(assign\)` + s = append(s, "d") // want `missing whitespace above this line \(no shared variables above assign\)` s = append(s, y) } @@ -64,5 +64,5 @@ func assignAfterBlock() { return } - x = 2 // want `missing whitespace decreases readability \(assign\)` + x = 2 // want `missing whitespace above this line \(invalid statement above assign\)` } diff --git a/testdata/src/default_config/branch/branch.go b/testdata/src/default_config/branch/branch.go index f7de78b..a16c923 100644 --- a/testdata/src/default_config/branch/branch.go +++ b/testdata/src/default_config/branch/branch.go @@ -70,28 +70,28 @@ func fn2() { fmt.Println("") fmt.Println("") fmt.Println("") - break // want `missing whitespace decreases readability \(branch\)` + break // want `missing whitespace above this line \(too many lines above branch\)` } if true { fmt.Println("") fmt.Println("") fmt.Println("") - continue // want `missing whitespace decreases readability \(branch\)` + continue // want `missing whitespace above this line \(too many lines above branch\)` } if true { fmt.Println("") fmt.Println("") fmt.Println("") - fallthrough // want `missing whitespace decreases readability \(branch\)` + fallthrough // want `missing whitespace above this line \(too many lines above branch\)` } if true { fmt.Println("") fmt.Println("") fmt.Println("") - goto START // want `missing whitespace decreases readability \(branch\)` + goto START // want `missing whitespace above this line \(too many lines above branch\)` } } } diff --git a/testdata/src/default_config/branch/branch.go.golden b/testdata/src/default_config/branch/branch.go.golden index 7824b12..de9c6bb 100644 --- a/testdata/src/default_config/branch/branch.go.golden +++ b/testdata/src/default_config/branch/branch.go.golden @@ -71,7 +71,7 @@ func fn2() { fmt.Println("") fmt.Println("") - break // want `missing whitespace decreases readability \(branch\)` + break // want `missing whitespace above this line \(too many lines above branch\)` } if true { @@ -79,7 +79,7 @@ func fn2() { fmt.Println("") fmt.Println("") - continue // want `missing whitespace decreases readability \(branch\)` + continue // want `missing whitespace above this line \(too many lines above branch\)` } if true { @@ -87,7 +87,7 @@ func fn2() { fmt.Println("") fmt.Println("") - fallthrough // want `missing whitespace decreases readability \(branch\)` + fallthrough // want `missing whitespace above this line \(too many lines above branch\)` } if true { @@ -95,7 +95,7 @@ func fn2() { fmt.Println("") fmt.Println("") - goto START // want `missing whitespace decreases readability \(branch\)` + goto START // want `missing whitespace above this line \(too many lines above branch\)` } } } diff --git a/testdata/src/default_config/decl/decl.go b/testdata/src/default_config/decl/decl.go index 379e820..80c5ead 100644 --- a/testdata/src/default_config/decl/decl.go +++ b/testdata/src/default_config/decl/decl.go @@ -2,28 +2,28 @@ package testpkg func fn1() { var a = 1 - var b = 2 // want `missing whitespace decreases readability \(decl\)` + var b = 2 // want `missing whitespace above this line \(never cuddle decl\)` const c = 3 - const d = 4 // want `missing whitespace decreases readability \(decl\)` + const d = 4 // want `missing whitespace above this line \(never cuddle decl\)` e := 5 - var f = 6 // want `missing whitespace decreases readability \(decl\)` - g := 7 // want `missing whitespace decreases readability \(assign\)` + var f = 6 // want `missing whitespace above this line \(never cuddle decl\)` + g := 7 // want `missing whitespace above this line \(invalid statement above assign\)` } func fn2() { var a = 1 - var b = a // want `missing whitespace decreases readability \(decl\)` + var b = a // want `missing whitespace above this line \(never cuddle decl\)` } func fn3() { a := 1 - var b = a // want `missing whitespace decreases readability \(decl\)` + var b = a // want `missing whitespace above this line \(never cuddle decl\)` } func fn4() { - var x = func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + var x = func() { // want +1 `unnecessary whitespace \(leading-whitespace\)` return 1 }() diff --git a/testdata/src/default_config/decl/decl.go.golden b/testdata/src/default_config/decl/decl.go.golden index 192ee79..c6601d6 100644 --- a/testdata/src/default_config/decl/decl.go.golden +++ b/testdata/src/default_config/decl/decl.go.golden @@ -3,33 +3,33 @@ package testpkg func fn1() { var a = 1 - var b = 2 // want `missing whitespace decreases readability \(decl\)` + var b = 2 // want `missing whitespace above this line \(never cuddle decl\)` const c = 3 - const d = 4 // want `missing whitespace decreases readability \(decl\)` + const d = 4 // want `missing whitespace above this line \(never cuddle decl\)` e := 5 - var f = 6 // want `missing whitespace decreases readability \(decl\)` + var f = 6 // want `missing whitespace above this line \(never cuddle decl\)` - g := 7 // want `missing whitespace decreases readability \(assign\)` + g := 7 // want `missing whitespace above this line \(invalid statement above assign\)` } func fn2() { var a = 1 - var b = a // want `missing whitespace decreases readability \(decl\)` + var b = a // want `missing whitespace above this line \(never cuddle decl\)` } func fn3() { a := 1 - var b = a // want `missing whitespace decreases readability \(decl\)` + var b = a // want `missing whitespace above this line \(never cuddle decl\)` } func fn4() { - var x = func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + var x = func() { // want +1 `unnecessary whitespace \(leading-whitespace\)` return 1 }() } diff --git a/testdata/src/default_config/defer/defer.go b/testdata/src/default_config/defer/defer.go index 3839b35..cdae75a 100644 --- a/testdata/src/default_config/defer/defer.go +++ b/testdata/src/default_config/defer/defer.go @@ -31,12 +31,12 @@ func fn() { a = Fn() defer a.Close() - b = Fn() // want `missing whitespace decreases readability \(assign\)` + b = Fn() // want `missing whitespace above this line \(invalid statement above assign\)` defer b.Close() a = Fn() b = Fn() - defer a.Close() // want `missing whitespace decreases readability \(defer\)` + defer a.Close() // want `missing whitespace above this line \(no shared variables above defer\)` defer b.Close() m := sync.Mutex{} @@ -45,7 +45,7 @@ func fn() { defer m.Unlock() c := true - defer func(b bool) { // want `missing whitespace decreases readability \(defer\)` + defer func(b bool) { // want `missing whitespace above this line \(no shared variables above defer\)` fmt.Printf("%v", b) }() @@ -54,7 +54,7 @@ func fn() { func fn2() { a := 1 - b := Fn() // want `missing whitespace decreases readability \(defer\)` + b := Fn() // want `missing whitespace above this line \(too many statements above defer\)` defer b.Close() _ = a diff --git a/testdata/src/default_config/defer/defer.go.golden b/testdata/src/default_config/defer/defer.go.golden index 74c2eb5..d042030 100644 --- a/testdata/src/default_config/defer/defer.go.golden +++ b/testdata/src/default_config/defer/defer.go.golden @@ -32,13 +32,13 @@ func fn() { a = Fn() defer a.Close() - b = Fn() // want `missing whitespace decreases readability \(assign\)` + b = Fn() // want `missing whitespace above this line \(invalid statement above assign\)` defer b.Close() a = Fn() b = Fn() - defer a.Close() // want `missing whitespace decreases readability \(defer\)` + defer a.Close() // want `missing whitespace above this line \(no shared variables above defer\)` defer b.Close() m := sync.Mutex{} @@ -48,7 +48,7 @@ func fn() { c := true - defer func(b bool) { // want `missing whitespace decreases readability \(defer\)` + defer func(b bool) { // want `missing whitespace above this line \(no shared variables above defer\)` fmt.Printf("%v", b) }() @@ -58,7 +58,7 @@ func fn() { func fn2() { a := 1 - b := Fn() // want `missing whitespace decreases readability \(defer\)` + b := Fn() // want `missing whitespace above this line \(too many statements above defer\)` defer b.Close() _ = a diff --git a/testdata/src/default_config/expr/expr.go b/testdata/src/default_config/expr/expr.go index c91fc6a..ca608a1 100644 --- a/testdata/src/default_config/expr/expr.go +++ b/testdata/src/default_config/expr/expr.go @@ -17,8 +17,8 @@ func fn() { func fn2() { a := 1 b := 2 - fmt.Println("") // want `missing whitespace decreases readability \(expr\)` - c := 3 // want `missing whitespace decreases readability \(assign\)` + fmt.Println("") // want `missing whitespace above this line \(no shared variables above expr\)` + c := 3 // want `missing whitespace above this line \(invalid statement above assign\)` d := 4 _ = a @@ -31,7 +31,7 @@ func fn3() { a := 1 b := 2 fmt.Println(b) - c := 3 // want `missing whitespace decreases readability \(assign\)` + c := 3 // want `missing whitespace above this line \(invalid statement above assign\)` d := 4 _ = a @@ -42,5 +42,5 @@ func fn3() { func fn4() { b := 2 - <-ch // want `missing whitespace decreases readability \(expr\)` + <-ch // want `missing whitespace above this line \(no shared variables above expr\)` } diff --git a/testdata/src/default_config/expr/expr.go.golden b/testdata/src/default_config/expr/expr.go.golden index e8cc223..dcedb91 100644 --- a/testdata/src/default_config/expr/expr.go.golden +++ b/testdata/src/default_config/expr/expr.go.golden @@ -18,9 +18,9 @@ func fn2() { a := 1 b := 2 - fmt.Println("") // want `missing whitespace decreases readability \(expr\)` + fmt.Println("") // want `missing whitespace above this line \(no shared variables above expr\)` - c := 3 // want `missing whitespace decreases readability \(assign\)` + c := 3 // want `missing whitespace above this line \(invalid statement above assign\)` d := 4 _ = a @@ -34,7 +34,7 @@ func fn3() { b := 2 fmt.Println(b) - c := 3 // want `missing whitespace decreases readability \(assign\)` + c := 3 // want `missing whitespace above this line \(invalid statement above assign\)` d := 4 _ = a @@ -46,5 +46,5 @@ func fn3() { func fn4() { b := 2 - <-ch // want `missing whitespace decreases readability \(expr\)` + <-ch // want `missing whitespace above this line \(no shared variables above expr\)` } diff --git a/testdata/src/default_config/for/for.go b/testdata/src/default_config/for/for.go index 3488ef9..c5ff1dd 100644 --- a/testdata/src/default_config/for/for.go +++ b/testdata/src/default_config/for/for.go @@ -2,7 +2,7 @@ package testpkg func fn1() { a := 1 - b := 2 // want `missing whitespace decreases readability \(for\)` + b := 2 // want `missing whitespace above this line \(too many statements above for\)` for i := 0; i < b; i++ { panic(1) } @@ -14,7 +14,7 @@ func fn1() { func fn2() { b := 2 a := 1 - for i := 0; i < b; i++ { // want `missing whitespace decreases readability \(for\)` + for i := 0; i < b; i++ { // want `missing whitespace above this line \(no shared variables above for\)` panic(1) } @@ -26,7 +26,7 @@ func fn3() { for i := 0; i < 1; i++ { panic("") } - for i := 0; i < 1; i++ { // want `missing whitespace decreases readability \(for\)` + for i := 0; i < 1; i++ { // want `missing whitespace above this line \(invalid statement above for\)` panic("") } } diff --git a/testdata/src/default_config/for/for.go.golden b/testdata/src/default_config/for/for.go.golden index e14729e..0059a77 100644 --- a/testdata/src/default_config/for/for.go.golden +++ b/testdata/src/default_config/for/for.go.golden @@ -3,7 +3,7 @@ package testpkg func fn1() { a := 1 - b := 2 // want `missing whitespace decreases readability \(for\)` + b := 2 // want `missing whitespace above this line \(too many statements above for\)` for i := 0; i < b; i++ { panic(1) } @@ -16,7 +16,7 @@ func fn2() { b := 2 a := 1 - for i := 0; i < b; i++ { // want `missing whitespace decreases readability \(for\)` + for i := 0; i < b; i++ { // want `missing whitespace above this line \(no shared variables above for\)` panic(1) } @@ -29,7 +29,7 @@ func fn3() { panic("") } - for i := 0; i < 1; i++ { // want `missing whitespace decreases readability \(for\)` + for i := 0; i < 1; i++ { // want `missing whitespace above this line \(invalid statement above for\)` panic("") } } diff --git a/testdata/src/default_config/go/go.go b/testdata/src/default_config/go/go.go index 58e9257..9a9c6b7 100644 --- a/testdata/src/default_config/go/go.go +++ b/testdata/src/default_config/go/go.go @@ -13,7 +13,7 @@ func Go() { go fooFunc() barFunc := func() {} - go fooFunc() // want `missing whitespace decreases readability \(go\)` + go fooFunc() // want `missing whitespace above this line \(no shared variables above go\)` _ = barFunc @@ -22,7 +22,7 @@ func Go() { }() cuddled := true - go func() { // want `missing whitespace decreases readability \(go\)` + go func() { // want `missing whitespace above this line \(no shared variables above go\)` fmt.Println("hey") }() @@ -32,7 +32,7 @@ func Go() { go Fn(argToGo) notArgToGo := 1 - go Fn(argToGo) // want `missing whitespace decreases readability \(go\)` + go Fn(argToGo) // want `missing whitespace above this line \(no shared variables above go\)` _ = notArgToGo @@ -45,13 +45,13 @@ func Go() { go t3() multiCuddle1 := NewT() - multiCuddle2 := NewT() // want `missing whitespace decreases readability \(go\)` + multiCuddle2 := NewT() // want `missing whitespace above this line \(too many statements above go\)` go multiCuddle2() // TODO: Breaking change, this used to be on the first `go` stmt - now it's // on the line that should have a blank line above. t4 := NewT() - t5 := NewT() // want `missing whitespace decreases readability \(go\)` + t5 := NewT() // want `missing whitespace above this line \(too many statements above go\)` go t5() go t4() diff --git a/testdata/src/default_config/go/go.go.golden b/testdata/src/default_config/go/go.go.golden index c4dd006..a1d9b7d 100644 --- a/testdata/src/default_config/go/go.go.golden +++ b/testdata/src/default_config/go/go.go.golden @@ -15,7 +15,7 @@ func Go() { barFunc := func() {} - go fooFunc() // want `missing whitespace decreases readability \(go\)` + go fooFunc() // want `missing whitespace above this line \(no shared variables above go\)` _ = barFunc @@ -25,7 +25,7 @@ func Go() { cuddled := true - go func() { // want `missing whitespace decreases readability \(go\)` + go func() { // want `missing whitespace above this line \(no shared variables above go\)` fmt.Println("hey") }() @@ -36,7 +36,7 @@ func Go() { notArgToGo := 1 - go Fn(argToGo) // want `missing whitespace decreases readability \(go\)` + go Fn(argToGo) // want `missing whitespace above this line \(no shared variables above go\)` _ = notArgToGo @@ -50,14 +50,14 @@ func Go() { multiCuddle1 := NewT() - multiCuddle2 := NewT() // want `missing whitespace decreases readability \(go\)` + multiCuddle2 := NewT() // want `missing whitespace above this line \(too many statements above go\)` go multiCuddle2() // TODO: Breaking change, this used to be on the first `go` stmt - now it's // on the line that should have a blank line above. t4 := NewT() - t5 := NewT() // want `missing whitespace decreases readability \(go\)` + t5 := NewT() // want `missing whitespace above this line \(too many statements above go\)` go t5() go t4() diff --git a/testdata/src/default_config/if/if.go b/testdata/src/default_config/if/if.go index cfe1139..58f3673 100644 --- a/testdata/src/default_config/if/if.go +++ b/testdata/src/default_config/if/if.go @@ -4,7 +4,7 @@ import "errors" func fn1() { a := 1 - b := 2 // want `missing whitespace decreases readability \(if\)` + b := 2 // want `missing whitespace above this line \(too many statements above if\)` if b == 2 { panic(1) } @@ -16,7 +16,7 @@ func fn1() { func fn2() { b := 2 a := 1 - if b == 2 { // want `missing whitespace decreases readability \(if\)` + if b == 2 { // want `missing whitespace above this line \(no shared variables above if\)` panic(1) } @@ -36,7 +36,7 @@ func fn4() { if a := 1; a != 2 { panic(a) } - if a := 2; a != 2 { // want `missing whitespace decreases readability \(if\)` + if a := 2; a != 2 { // want `missing whitespace above this line \(invalid statement above if\)` panic(a) } } diff --git a/testdata/src/default_config/if/if.go.golden b/testdata/src/default_config/if/if.go.golden index b03e023..8c8f794 100644 --- a/testdata/src/default_config/if/if.go.golden +++ b/testdata/src/default_config/if/if.go.golden @@ -5,7 +5,7 @@ import "errors" func fn1() { a := 1 - b := 2 // want `missing whitespace decreases readability \(if\)` + b := 2 // want `missing whitespace above this line \(too many statements above if\)` if b == 2 { panic(1) } @@ -18,7 +18,7 @@ func fn2() { b := 2 a := 1 - if b == 2 { // want `missing whitespace decreases readability \(if\)` + if b == 2 { // want `missing whitespace above this line \(no shared variables above if\)` panic(1) } @@ -39,7 +39,7 @@ func fn4() { panic(a) } - if a := 2; a != 2 { // want `missing whitespace decreases readability \(if\)` + if a := 2; a != 2 { // want `missing whitespace above this line \(invalid statement above if\)` panic(a) } } diff --git a/testdata/src/default_config/label/label.go b/testdata/src/default_config/label/label.go index 2358049..381f69e 100644 --- a/testdata/src/default_config/label/label.go +++ b/testdata/src/default_config/label/label.go @@ -23,18 +23,19 @@ func fn2() { } } +// TODO: L2 should be valid? func fn3() { L1: if true { _ = 1 } -L2: // want `missing whitespace decreases readability \(if\)` +L2: // want `missing whitespace above this line \(invalid statement above if\)` if true { _ = 1 } _ = 1 -L3: // want `missing whitespace decreases readability \(label\)` +L3: // want `missing whitespace above this line \(never cuddle label\)` _ = 1 } @@ -45,6 +46,6 @@ LABEL: fmt.Println("") fmt.Println("") fmt.Println("") - break // want `missing whitespace decreases readability \(branch\)` + break // want `missing whitespace above this line \(too many lines above branch\)` } } diff --git a/testdata/src/default_config/label/label.go.golden b/testdata/src/default_config/label/label.go.golden index abbaad1..99850af 100644 --- a/testdata/src/default_config/label/label.go.golden +++ b/testdata/src/default_config/label/label.go.golden @@ -23,20 +23,21 @@ func fn2() { } } +// TODO: L2 should be valid? func fn3() { L1: if true { _ = 1 } -L2: // want `missing whitespace decreases readability \(if\)` +L2: // want `missing whitespace above this line \(invalid statement above if\)` if true { _ = 1 } _ = 1 -L3: // want `missing whitespace decreases readability \(label\)` +L3: // want `missing whitespace above this line \(never cuddle label\)` _ = 1 } @@ -48,6 +49,6 @@ LABEL: fmt.Println("") fmt.Println("") - break // want `missing whitespace decreases readability \(branch\)` + break // want `missing whitespace above this line \(too many lines above branch\)` } } diff --git a/testdata/src/default_config/range/range.go b/testdata/src/default_config/range/range.go index ee13f4f..0bee03d 100644 --- a/testdata/src/default_config/range/range.go +++ b/testdata/src/default_config/range/range.go @@ -2,7 +2,7 @@ package testpkg func fn1() { a := []int{} - b := []int{} // want `missing whitespace decreases readability \(range\)` + b := []int{} // want `missing whitespace above this line \(too many statements above range\)` for range b { panic(1) } @@ -14,7 +14,7 @@ func fn1() { func fn2() { b := []int{} a := []int{} - for range b { // want `missing whitespace decreases readability \(range\)` + for range b { // want `missing whitespace above this line \(no shared variables above range\)` panic(1) } @@ -26,7 +26,7 @@ func fn3() { for range make([]int, 0) { panic("") } - for range make([]int, 0) { // want `missing whitespace decreases readability \(range\)` + for range make([]int, 0) { // want `missing whitespace above this line \(invalid statement above range\)` panic("") } } diff --git a/testdata/src/default_config/range/range.go.golden b/testdata/src/default_config/range/range.go.golden index 8b2e3bf..316d4ca 100644 --- a/testdata/src/default_config/range/range.go.golden +++ b/testdata/src/default_config/range/range.go.golden @@ -3,7 +3,7 @@ package testpkg func fn1() { a := []int{} - b := []int{} // want `missing whitespace decreases readability \(range\)` + b := []int{} // want `missing whitespace above this line \(too many statements above range\)` for range b { panic(1) } @@ -16,7 +16,7 @@ func fn2() { b := []int{} a := []int{} - for range b { // want `missing whitespace decreases readability \(range\)` + for range b { // want `missing whitespace above this line \(no shared variables above range\)` panic(1) } @@ -29,7 +29,7 @@ func fn3() { panic("") } - for range make([]int, 0) { // want `missing whitespace decreases readability \(range\)` + for range make([]int, 0) { // want `missing whitespace above this line \(invalid statement above range\)` panic("") } } diff --git a/testdata/src/default_config/return/return.go b/testdata/src/default_config/return/return.go index f8d7740..5e9a7c7 100644 --- a/testdata/src/default_config/return/return.go +++ b/testdata/src/default_config/return/return.go @@ -12,29 +12,29 @@ func fn2() int { func fn3() int { _ = 1 _ = 2 - return 1 // want `missing whitespace decreases readability \(return\)` + return 1 // want `missing whitespace above this line \(too many lines above return\)` } func fn4() int { if true { _ = 1 } - return 1 // want `missing whitespace decreases readability \(return\)` + return 1 // want `missing whitespace above this line \(too many lines above return\)` } func fn5() int { if true { _ = 1 _ = 2 - return 1 // want `missing whitespace decreases readability \(return\)` + return 1 // want `missing whitespace above this line \(too many lines above return\)` } - return 1 // want `missing whitespace decreases readability \(return\)` + return 1 // want `missing whitespace above this line \(too many lines above return\)` } func fn6() func() { _ = 1 _ = 2 - return func() { // want `missing whitespace decreases readability \(return\)` + return func() { // want `missing whitespace above this line \(too many lines above return\)` _ = 1 } } diff --git a/testdata/src/default_config/return/return.go.golden b/testdata/src/default_config/return/return.go.golden index c5968b6..1a925d2 100644 --- a/testdata/src/default_config/return/return.go.golden +++ b/testdata/src/default_config/return/return.go.golden @@ -13,7 +13,7 @@ func fn3() int { _ = 1 _ = 2 - return 1 // want `missing whitespace decreases readability \(return\)` + return 1 // want `missing whitespace above this line \(too many lines above return\)` } func fn4() int { @@ -21,7 +21,7 @@ func fn4() int { _ = 1 } - return 1 // want `missing whitespace decreases readability \(return\)` + return 1 // want `missing whitespace above this line \(too many lines above return\)` } func fn5() int { @@ -29,17 +29,17 @@ func fn5() int { _ = 1 _ = 2 - return 1 // want `missing whitespace decreases readability \(return\)` + return 1 // want `missing whitespace above this line \(too many lines above return\)` } - return 1 // want `missing whitespace decreases readability \(return\)` + return 1 // want `missing whitespace above this line \(too many lines above return\)` } func fn6() func() { _ = 1 _ = 2 - return func() { // want `missing whitespace decreases readability \(return\)` + return func() { // want `missing whitespace above this line \(too many lines above return\)` _ = 1 } } diff --git a/testdata/src/default_config/select/select.go b/testdata/src/default_config/select/select.go index cef333c..c8d8844 100644 --- a/testdata/src/default_config/select/select.go +++ b/testdata/src/default_config/select/select.go @@ -15,7 +15,7 @@ func fn1(ctx context.Context, ch1 chan struct{}) { func fn1(ctx context.Context, ch1 chan struct{}) { x := 1 - select { // want `missing whitespace decreases readability \(select\)` + select { // want `missing whitespace above this line \(no shared variables above select\)` case ctx.Done(): _ = 1 case <-ch1: diff --git a/testdata/src/default_config/select/select.go.golden b/testdata/src/default_config/select/select.go.golden index b554ad7..6090756 100644 --- a/testdata/src/default_config/select/select.go.golden +++ b/testdata/src/default_config/select/select.go.golden @@ -16,7 +16,7 @@ func fn1(ctx context.Context, ch1 chan struct{}) { func fn1(ctx context.Context, ch1 chan struct{}) { x := 1 - select { // want `missing whitespace decreases readability \(select\)` + select { // want `missing whitespace above this line \(no shared variables above select\)` case ctx.Done(): _ = 1 case <-ch1: diff --git a/testdata/src/default_config/switch/switch.go b/testdata/src/default_config/switch/switch.go index 0159724..86dc03f 100644 --- a/testdata/src/default_config/switch/switch.go +++ b/testdata/src/default_config/switch/switch.go @@ -2,7 +2,7 @@ package testpkg func fn1() { a := 1 - b := 2 // want `missing whitespace decreases readability \(switch\)` + b := 2 // want `missing whitespace above this line \(too many statements above switch\)` switch b { case 1: case 2: @@ -14,7 +14,7 @@ func fn1() { func fn2() { b := 2 a := 1 - switch b { // want `missing whitespace decreases readability \(switch\)` + switch b { // want `missing whitespace above this line \(no shared variables above switch\)` case 1: case 2: case 3: @@ -27,7 +27,7 @@ func fn3() { case true: case false: } - switch true { // want `missing whitespace decreases readability \(switch\)` + switch true { // want `missing whitespace above this line \(invalid statement above switch\)` case true: case false: panic("") diff --git a/testdata/src/default_config/switch/switch.go.golden b/testdata/src/default_config/switch/switch.go.golden index ef88e63..1f1ad06 100644 --- a/testdata/src/default_config/switch/switch.go.golden +++ b/testdata/src/default_config/switch/switch.go.golden @@ -3,7 +3,7 @@ package testpkg func fn1() { a := 1 - b := 2 // want `missing whitespace decreases readability \(switch\)` + b := 2 // want `missing whitespace above this line \(too many statements above switch\)` switch b { case 1: case 2: @@ -16,7 +16,7 @@ func fn2() { b := 2 a := 1 - switch b { // want `missing whitespace decreases readability \(switch\)` + switch b { // want `missing whitespace above this line \(no shared variables above switch\)` case 1: case 2: case 3: @@ -30,7 +30,7 @@ func fn3() { case false: } - switch true { // want `missing whitespace decreases readability \(switch\)` + switch true { // want `missing whitespace above this line \(invalid statement above switch\)` case true: case false: panic("") diff --git a/testdata/src/default_config/type_switch/type_switch.go b/testdata/src/default_config/type_switch/type_switch.go index 3bb8089..8e832b0 100644 --- a/testdata/src/default_config/type_switch/type_switch.go +++ b/testdata/src/default_config/type_switch/type_switch.go @@ -18,7 +18,7 @@ func fn2() { var a any = 1 b := 1 - switch a.(type) { // want `missing whitespace decreases readability \(type-switch\)` + switch a.(type) { // want `missing whitespace above this line \(no shared variables above type-switch\)` case int: case string: } @@ -41,7 +41,7 @@ func fn3(in any) { func fn4(in any) { a := Fn() - b := Fn() // want `missing whitespace decreases readability \(type-switch\)` + b := Fn() // want `missing whitespace above this line \(too many statements above type-switch\)` switch b.(type) { case string: return diff --git a/testdata/src/default_config/type_switch/type_switch.go.golden b/testdata/src/default_config/type_switch/type_switch.go.golden index 5aef16e..3a4b54c 100644 --- a/testdata/src/default_config/type_switch/type_switch.go.golden +++ b/testdata/src/default_config/type_switch/type_switch.go.golden @@ -19,7 +19,7 @@ func fn2() { b := 1 - switch a.(type) { // want `missing whitespace decreases readability \(type-switch\)` + switch a.(type) { // want `missing whitespace above this line \(no shared variables above type-switch\)` case int: case string: } @@ -43,7 +43,7 @@ func fn3(in any) { func fn4(in any) { a := Fn() - b := Fn() // want `missing whitespace decreases readability \(type-switch\)` + b := Fn() // want `missing whitespace above this line \(too many statements above type-switch\)` switch b.(type) { case string: return diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go b/testdata/src/default_config/whitespace/leading_whitespace.go index 0336071..ec60f77 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go +++ b/testdata/src/default_config/whitespace/leading_whitespace.go @@ -8,18 +8,18 @@ func Call(fn func()) func() { func fn0() { fmt.Println("Hello, World") } -func fn1() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn1() { // want +1 `unnecessary whitespace \(leading-whitespace\)` fmt.Println("Hello, World") } -func fn2() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn2() { // want +1 `unnecessary whitespace \(leading-whitespace\)` // Space before comment. fmt.Println("Hello, World") } -func fn3() { // want +2 `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn3() { // want +2 `unnecessary whitespace \(leading-whitespace\)` // Space after comment fmt.Println("Hello, World") @@ -42,7 +42,7 @@ func fn51() { } func fn6() { - if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + if true { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } @@ -51,56 +51,56 @@ func fn6() { func fn7() { if true { _ = 1 - } else if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else if true { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 - } else { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } } -func fn8(a string, b any, s []string) { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn8(a string, b any, s []string) { // want +1 `unnecessary whitespace \(leading-whitespace\)` - if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + if true { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 - } else if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else if true { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } - for i := 0; i < 1; i++ { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + for i := 0; i < 1; i++ { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } - for n := range []int{} { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + for n := range []int{} { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = n } - for range s { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + for range s { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } - switch a { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + switch a { // want +1 `unnecessary whitespace \(leading-whitespace\)` case "a": } - switch b.(type) { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + switch b.(type) { // want +1 `unnecessary whitespace \(leading-whitespace\)` case int: } - f := func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + f := func() { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } - f2 := Call(func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + f2 := Call(func() { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 }) @@ -111,16 +111,16 @@ func fn8(a string, b any, s []string) { // want +1 `unnecessary whitespace decre func fn9() { switch { - case 1: // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + case 1: // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 - case 2: // want +2 `unnecessary whitespace decreases readability \(leading-whitespace\)` + case 2: // want +2 `unnecessary whitespace \(leading-whitespace\)` // This is a comment _ = 2 - default: // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + default: // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 3 diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go.golden b/testdata/src/default_config/whitespace/leading_whitespace.go.golden index 7cc7cdf..cda122d 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go.golden +++ b/testdata/src/default_config/whitespace/leading_whitespace.go.golden @@ -8,16 +8,16 @@ func Call(fn func()) func() { func fn0() { fmt.Println("Hello, World") } -func fn1() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn1() { // want +1 `unnecessary whitespace \(leading-whitespace\)` fmt.Println("Hello, World") } -func fn2() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn2() { // want +1 `unnecessary whitespace \(leading-whitespace\)` // Space before comment. fmt.Println("Hello, World") } -func fn3() { // want +2 `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn3() { // want +2 `unnecessary whitespace \(leading-whitespace\)` // Space after comment fmt.Println("Hello, World") } @@ -39,7 +39,7 @@ func fn51() { } func fn6() { - if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + if true { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } } @@ -47,45 +47,45 @@ func fn6() { func fn7() { if true { _ = 1 - } else if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else if true { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 - } else { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } } -func fn8(a string, b any, s []string) { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` - if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` +func fn8(a string, b any, s []string) { // want +1 `unnecessary whitespace \(leading-whitespace\)` + if true { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 - } else if true { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + } else if true { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } - for i := 0; i < 1; i++ { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + for i := 0; i < 1; i++ { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } - for n := range []int{} { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + for n := range []int{} { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = n } - for range s { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + for range s { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } - switch a { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + switch a { // want +1 `unnecessary whitespace \(leading-whitespace\)` case "a": } - switch b.(type) { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + switch b.(type) { // want +1 `unnecessary whitespace \(leading-whitespace\)` case int: } - f := func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + f := func() { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 } - f2 := Call(func() { // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + f2 := Call(func() { // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 }) @@ -95,14 +95,14 @@ func fn8(a string, b any, s []string) { // want +1 `unnecessary whitespace decre func fn9() { switch { - case 1: // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + case 1: // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 1 - case 2: // want +2 `unnecessary whitespace decreases readability \(leading-whitespace\)` + case 2: // want +2 `unnecessary whitespace \(leading-whitespace\)` // This is a comment _ = 2 - default: // want +1 `unnecessary whitespace decreases readability \(leading-whitespace\)` + default: // want +1 `unnecessary whitespace \(leading-whitespace\)` _ = 3 } diff --git a/testdata/src/default_config/whitespace/trailing_whitespace.go b/testdata/src/default_config/whitespace/trailing_whitespace.go index 1b0af75..03560da 100644 --- a/testdata/src/default_config/whitespace/trailing_whitespace.go +++ b/testdata/src/default_config/whitespace/trailing_whitespace.go @@ -7,12 +7,12 @@ func Call(fn func()) func() { } func fn1() { - fmt.Println("Hello, World") // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + fmt.Println("Hello, World") // want +1 `unnecessary whitespace \(trailing-whitespace\)` } func fn2() { - fmt.Println("Hello, World") // want +2 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + fmt.Println("Hello, World") // want +2 `unnecessary whitespace \(trailing-whitespace\)` // Comment with wihtespace } @@ -31,45 +31,45 @@ func fn4() { func fn5() { if true { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } } func fn6() { if true { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } else if true { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } else { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } } func fn8(a string, b any, s []string) { if true { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } else if true { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } for i := 0; i < 1; i++ { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } for n := range []int{} { - _ = n // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = n // want +1 `unnecessary whitespace \(trailing-whitespace\)` } for range s { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } @@ -84,12 +84,12 @@ func fn8(a string, b any, s []string) { } f := func() { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } f2 := Call(func() { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` }) diff --git a/testdata/src/default_config/whitespace/trailing_whitespace.go.golden b/testdata/src/default_config/whitespace/trailing_whitespace.go.golden index 50cb73c..12d664f 100644 --- a/testdata/src/default_config/whitespace/trailing_whitespace.go.golden +++ b/testdata/src/default_config/whitespace/trailing_whitespace.go.golden @@ -7,11 +7,11 @@ func Call(fn func()) func() { } func fn1() { - fmt.Println("Hello, World") // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + fmt.Println("Hello, World") // want +1 `unnecessary whitespace \(trailing-whitespace\)` } func fn2() { - fmt.Println("Hello, World") // want +2 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + fmt.Println("Hello, World") // want +2 `unnecessary whitespace \(trailing-whitespace\)` // Comment with wihtespace } @@ -29,37 +29,37 @@ func fn4() { func fn5() { if true { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } } func fn6() { if true { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } else if true { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } else { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } } func fn8(a string, b any, s []string) { if true { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } else if true { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } for i := 0; i < 1; i++ { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } for n := range []int{} { - _ = n // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = n // want +1 `unnecessary whitespace \(trailing-whitespace\)` } for range s { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } switch a { @@ -73,11 +73,11 @@ func fn8(a string, b any, s []string) { } f := func() { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` } f2 := Call(func() { - _ = 1 // want +1 `unnecessary whitespace decreases readability \(trailing-whitespace\)` + _ = 1 // want +1 `unnecessary whitespace \(trailing-whitespace\)` }) _ = f diff --git a/testdata/src/with_config/branch_max_lines/branch.go b/testdata/src/with_config/branch_max_lines/branch.go index 68653b9..710dbd4 100644 --- a/testdata/src/with_config/branch_max_lines/branch.go +++ b/testdata/src/with_config/branch_max_lines/branch.go @@ -14,5 +14,5 @@ func fn2() int { _ = 3 _ = 4 _ = 5 - return 1 // want `missing whitespace decreases readability \(return\)` + return 1 // want `missing whitespace above this line \(too many lines above return\)` } diff --git a/testdata/src/with_config/branch_max_lines/branch.go.golden b/testdata/src/with_config/branch_max_lines/branch.go.golden index 6524ba3..ccc96e3 100644 --- a/testdata/src/with_config/branch_max_lines/branch.go.golden +++ b/testdata/src/with_config/branch_max_lines/branch.go.golden @@ -15,6 +15,6 @@ func fn2() int { _ = 4 _ = 5 - return 1 // want `missing whitespace decreases readability \(return\)` + return 1 // want `missing whitespace above this line \(too many lines above return\)` } diff --git a/testdata/src/with_config/case_max_lines/case.go b/testdata/src/with_config/case_max_lines/case.go index 0a9dd6b..f07becd 100644 --- a/testdata/src/with_config/case_max_lines/case.go +++ b/testdata/src/with_config/case_max_lines/case.go @@ -12,7 +12,7 @@ func fn1(n int) { case 3: n++ n++ - n++ // want `missing whitespace decreases readability \(case-trailing-newline\)` + n++ // want `missing whitespace below this line \(case-trailing-newline\)` case 4: n++ n++ @@ -32,7 +32,7 @@ func fn2(ctx context.Context, ch1 chan struct{}) { case ctx.Done(): _ = 1 _ = 1 - _ = 1 // want `missing whitespace decreases readability \(case-trailing-newline\)` + _ = 1 // want `missing whitespace below this line \(case-trailing-newline\)` case <-ch1: _ = 1 default: diff --git a/testdata/src/with_config/case_max_lines/case.go.golden b/testdata/src/with_config/case_max_lines/case.go.golden index dcedd88..fa6e278 100644 --- a/testdata/src/with_config/case_max_lines/case.go.golden +++ b/testdata/src/with_config/case_max_lines/case.go.golden @@ -12,7 +12,7 @@ func fn1(n int) { case 3: n++ n++ - n++ // want `missing whitespace decreases readability \(case-trailing-newline\)` + n++ // want `missing whitespace below this line \(case-trailing-newline\)` case 4: n++ @@ -33,7 +33,7 @@ func fn2(ctx context.Context, ch1 chan struct{}) { case ctx.Done(): _ = 1 _ = 1 - _ = 1 // want `missing whitespace decreases readability \(case-trailing-newline\)` + _ = 1 // want `missing whitespace below this line \(case-trailing-newline\)` case <-ch1: _ = 1 diff --git a/testdata/src/with_config/exclusive_short_decl/code.go b/testdata/src/with_config/exclusive_short_decl/code.go index 1c7755d..d150535 100644 --- a/testdata/src/with_config/exclusive_short_decl/code.go +++ b/testdata/src/with_config/exclusive_short_decl/code.go @@ -10,6 +10,6 @@ func fn() { c = 1 d := 1 - a = 3 // want `missing whitespace decreases readability \(assign\)` - e := 4 // want `missing whitespace decreases readability \(assign\)` + a = 3 // want `missing whitespace above this line \(invalid statement above assign\)` + e := 4 // want `missing whitespace above this line \(invalid statement above assign\)` } diff --git a/testdata/src/with_config/exclusive_short_decl/code.go.golden b/testdata/src/with_config/exclusive_short_decl/code.go.golden index 3c2e11f..68320ed 100644 --- a/testdata/src/with_config/exclusive_short_decl/code.go.golden +++ b/testdata/src/with_config/exclusive_short_decl/code.go.golden @@ -11,7 +11,7 @@ func fn() { d := 1 - a = 3 // want `missing whitespace decreases readability \(assign\)` + a = 3 // want `missing whitespace above this line \(invalid statement above assign\)` - e := 4 // want `missing whitespace decreases readability \(assign\)` + e := 4 // want `missing whitespace above this line \(invalid statement above assign\)` } diff --git a/testdata/src/with_config/first_in_block_n1/block.go b/testdata/src/with_config/first_in_block_n1/block.go index b1cfd1a..e218e01 100644 --- a/testdata/src/with_config/first_in_block_n1/block.go +++ b/testdata/src/with_config/first_in_block_n1/block.go @@ -13,7 +13,7 @@ func fn1() { } b := 1 - if true { // want `missing whitespace decreases readability \(if\)` + if true { // want `missing whitespace above this line \(no shared variables above if\)` Fn(one) if false { diff --git a/testdata/src/with_config/first_in_block_n1/block.go.golden b/testdata/src/with_config/first_in_block_n1/block.go.golden index 6d0801a..97e063a 100644 --- a/testdata/src/with_config/first_in_block_n1/block.go.golden +++ b/testdata/src/with_config/first_in_block_n1/block.go.golden @@ -14,7 +14,7 @@ func fn1() { b := 1 - if true { // want `missing whitespace decreases readability \(if\)` + if true { // want `missing whitespace above this line \(no shared variables above if\)` Fn(one) if false { diff --git a/testdata/src/with_config/if_errcheck/if_errcheck.go b/testdata/src/with_config/if_errcheck/if_errcheck.go index aba93e2..5732f48 100644 --- a/testdata/src/with_config/if_errcheck/if_errcheck.go +++ b/testdata/src/with_config/if_errcheck/if_errcheck.go @@ -3,14 +3,14 @@ package testpkg import "errors" func fn1() { - err := errors.New("x") // want +1 `unnecessary whitespace decreases readability \(err\)` + err := errors.New("x") // want +1 `unnecessary whitespace \(err\)` if err != nil { panic(err) } } -func fn11() { // want +2 `unnecessary whitespace decreases readability \(err\)` +func fn11() { // want +2 `unnecessary whitespace \(err\)` err := errors.New("x") if err != nil { @@ -37,7 +37,7 @@ func fn13() { func fn2() { a := 1 - err := errors.New("x") // want +1 `unnecessary whitespace decreases readability \(err\)` + err := errors.New("x") // want +1 `unnecessary whitespace \(err\)` if err != nil { panic(err) @@ -47,7 +47,7 @@ func fn2() { } func fn21() { - a := 1 // want +2 `unnecessary whitespace decreases readability \(err\)` + a := 1 // want +2 `unnecessary whitespace \(err\)` err := errors.New("x") if err != nil { @@ -59,7 +59,7 @@ func fn21() { func fn3() { a := 1 - err := errors.New("x") // want `missing whitespace decreases readability \(if\)` + err := errors.New("x") // want `missing whitespace above this line \(too many statements above if\)` if err != nil { panic(err) } diff --git a/testdata/src/with_config/if_errcheck/if_errcheck.go.golden b/testdata/src/with_config/if_errcheck/if_errcheck.go.golden index 166902c..c7b6353 100644 --- a/testdata/src/with_config/if_errcheck/if_errcheck.go.golden +++ b/testdata/src/with_config/if_errcheck/if_errcheck.go.golden @@ -4,13 +4,13 @@ package testpkg import "errors" func fn1() { - err := errors.New("x") // want +1 `unnecessary whitespace decreases readability \(err\)` + err := errors.New("x") // want +1 `unnecessary whitespace \(err\)` if err != nil { panic(err) } } -func fn11() { // want +2 `unnecessary whitespace decreases readability \(err\)` +func fn11() { // want +2 `unnecessary whitespace \(err\)` err := errors.New("x") if err != nil { panic(err) @@ -37,7 +37,7 @@ func fn13() { func fn2() { a := 1 - err := errors.New("x") // want +1 `unnecessary whitespace decreases readability \(err\)` + err := errors.New("x") // want +1 `unnecessary whitespace \(err\)` if err != nil { panic(err) } @@ -46,7 +46,7 @@ func fn2() { } func fn21() { - a := 1 // want +2 `unnecessary whitespace decreases readability \(err\)` + a := 1 // want +2 `unnecessary whitespace \(err\)` err := errors.New("x") if err != nil { @@ -59,7 +59,7 @@ func fn21() { func fn3() { a := 1 - err := errors.New("x") // want `missing whitespace decreases readability \(if\)` + err := errors.New("x") // want `missing whitespace above this line \(too many statements above if\)` if err != nil { panic(err) } diff --git a/testdata/src/with_config/send/send.go b/testdata/src/with_config/send/send.go index d4d4ce8..54c44ab 100644 --- a/testdata/src/with_config/send/send.go +++ b/testdata/src/with_config/send/send.go @@ -8,12 +8,12 @@ func fn1(ch chan int) { ch <- someFn(b) c := 1 - ch <- 1 // want `missing whitespace decreases readability \(send\)` + ch <- 1 // want `missing whitespace above this line \(no shared variables above send\)` // TODO: Technically this is used first in block but there's no easy way to // figure out the first statement in the block so for now this is not valid. d := 2 - ch <- func() int { // want `missing whitespace decreases readability \(send\)` + ch <- func() int { // want `missing whitespace above this line \(no shared variables above send\)` return d }() } diff --git a/testdata/src/with_config/send/send.go.golden b/testdata/src/with_config/send/send.go.golden index d4f93b5..875719c 100644 --- a/testdata/src/with_config/send/send.go.golden +++ b/testdata/src/with_config/send/send.go.golden @@ -9,13 +9,13 @@ func fn1(ch chan int) { c := 1 - ch <- 1 // want `missing whitespace decreases readability \(send\)` + ch <- 1 // want `missing whitespace above this line \(no shared variables above send\)` // TODO: Technically this is used first in block but there's no easy way to // figure out the first statement in the block so for now this is not valid. d := 2 - ch <- func() int { // want `missing whitespace decreases readability \(send\)` + ch <- func() int { // want `missing whitespace above this line \(no shared variables above send\)` return d }() } diff --git a/testdata/src/with_config/whole_block/block.go b/testdata/src/with_config/whole_block/block.go index 90168b7..e7665e9 100644 --- a/testdata/src/with_config/whole_block/block.go +++ b/testdata/src/with_config/whole_block/block.go @@ -8,7 +8,7 @@ func fn1() { y, z := 1, 2 if false { z++ - if true { // want `missing whitespace decreases readability \(if\)` + if true { // want `missing whitespace above this line \(no shared variables above if\)` y++ x++ } @@ -60,7 +60,7 @@ func fn3(a, b, c itn) { } b = 1 - for range make([]int, 10) { // want `missing whitespace decreases readability \(range\)` + for range make([]int, 10) { // want `missing whitespace above this line \(no shared variables above range\)` if true { c++ @@ -78,7 +78,7 @@ func fn3(a, b, c itn) { } b = 1 - for i := 0; i < 3; i++ { // want `missing whitespace decreases readability \(for\)` + for i := 0; i < 3; i++ { // want `missing whitespace above this line \(no shared variables above for\)` if true { c++ @@ -99,7 +99,7 @@ func fn4(a, b, c int) { } b = 1 - switch { // want `missing whitespace decreases readability \(switch\)` + switch { // want `missing whitespace above this line \(no shared variables above switch\)` case true: c++ a++ @@ -117,7 +117,7 @@ func fn5(a, b, c int) { }() b = 1 - go func() { // want `missing whitespace decreases readability \(go\)` + go func() { // want `missing whitespace above this line \(no shared variables above go\)` c++ a++ }() @@ -133,7 +133,7 @@ func fn6(a, b, c int) { }() b = 1 - defer func() { // want `missing whitespace decreases readability \(defer\)` + defer func() { // want `missing whitespace above this line \(no shared variables above defer\)` c++ a++ }() diff --git a/testdata/src/with_config/whole_block/block.go.golden b/testdata/src/with_config/whole_block/block.go.golden index 8dc5764..576ca62 100644 --- a/testdata/src/with_config/whole_block/block.go.golden +++ b/testdata/src/with_config/whole_block/block.go.golden @@ -9,7 +9,7 @@ func fn1() { if false { z++ - if true { // want `missing whitespace decreases readability \(if\)` + if true { // want `missing whitespace above this line \(no shared variables above if\)` y++ x++ } @@ -62,7 +62,7 @@ func fn3(a, b, c itn) { b = 1 - for range make([]int, 10) { // want `missing whitespace decreases readability \(range\)` + for range make([]int, 10) { // want `missing whitespace above this line \(no shared variables above range\)` if true { c++ @@ -81,7 +81,7 @@ func fn3(a, b, c itn) { b = 1 - for i := 0; i < 3; i++ { // want `missing whitespace decreases readability \(for\)` + for i := 0; i < 3; i++ { // want `missing whitespace above this line \(no shared variables above for\)` if true { c++ @@ -103,7 +103,7 @@ func fn4(a, b, c int) { b = 1 - switch { // want `missing whitespace decreases readability \(switch\)` + switch { // want `missing whitespace above this line \(no shared variables above switch\)` case true: c++ a++ @@ -122,7 +122,7 @@ func fn5(a, b, c int) { b = 1 - go func() { // want `missing whitespace decreases readability \(go\)` + go func() { // want `missing whitespace above this line \(no shared variables above go\)` c++ a++ }() @@ -139,7 +139,7 @@ func fn6(a, b, c int) { b = 1 - defer func() { // want `missing whitespace decreases readability \(defer\)` + defer func() { // want `missing whitespace above this line \(no shared variables above defer\)` c++ a++ }() diff --git a/wsl.go b/wsl.go index 74b6037..35572c7 100644 --- a/wsl.go +++ b/wsl.go @@ -11,8 +11,9 @@ import ( ) const ( - MessageAddWhitespace = "missing whitespace decreases readability" - MessageRemoveWhitespace = "unnecessary whitespace decreases readability" + MessageMissingWhitespaceAbove = "missing whitespace above this line" + MessageMissingWhitespaceBelow = "missing whitespace below this line" + MessageRemoveWhitespace = "unnecessary whitespace" ) type FixRange struct { @@ -107,7 +108,7 @@ func (w *WSL) checkCuddlingWithDecl( // We're cuddled but not with an assign, declare or defer statement which is // never allowed. if !nodeIsAssignDeclOrIncDec(previousNode) && !currIsDefer { - w.addError(cursor.Stmt().Pos(), cursor.Stmt().Pos(), cursor.Stmt().Pos(), MessageAddWhitespace, cursor.checkType) + w.addErrorInvalidTypeCuddle(cursor.Stmt().Pos(), cursor.checkType) return } @@ -116,7 +117,7 @@ func (w *WSL) checkCuddlingWithDecl( if len(anyIntersects) > 0 { // We have matches, but too many statements above. if maxAllowedStatements != -1 && numStmtsAbove > maxAllowedStatements { - w.addError(previousNode.Pos(), previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace, cursor.checkType) + w.addErrorTooManyStatements(previousNode.Pos(), cursor.checkType) } return true @@ -129,7 +130,6 @@ func (w *WSL) checkCuddlingWithDecl( // (including recursive blocks). if w.Config.AllowWholeBlock { allIdentsInBlock := identsFromNode(stmt, false) - if checkIntersection(allIdentsInBlock) { return } @@ -138,13 +138,12 @@ func (w *WSL) checkCuddlingWithDecl( // FEATURE(AllowFirstInBlock): Allow identifiers used first in block. if !w.Config.AllowWholeBlock && w.Config.AllowFirstInBlock { firstStmtIdents := identsFromNode(firstBlockStmt, true) - if checkIntersection(firstStmtIdents) { return } } - currentIdents := identsFromNode(cursor.Stmt(), true) + currentIdents := identsFromNode(stmt, true) if checkIntersection(currentIdents) { return } @@ -156,7 +155,7 @@ func (w *WSL) checkCuddlingWithDecl( // We're cuddled but the line immediately above doesn't contain any // variables used in this statement. - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) + w.addErrorNoIntersection(stmt.Pos(), cursor.checkType) } func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { @@ -208,7 +207,7 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { } if w.numberOfStatementsAbove(cursor) > 0 && !prevIsValidType { - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) + w.addErrorInvalidTypeCuddle(stmt.Pos(), cursor.checkType) } } @@ -293,9 +292,10 @@ func (w *WSL) checkError( // We report this fix on the same pos as the previous diagnostic, but the // fix is different. The reason is to just stack more fixes for the same - // diagnostic, the issue isn't present until the first fix. + // diagnostic, the issue isn't present until the first fix so this message + // will never be shown to the user. if w.numberOfStatementsAbove(cursor) > 0 { - w.addError(previousNodeEnd+1, previousNode.Pos(), previousNode.Pos(), MessageAddWhitespace, cursor.checkType) + w.addError(previousNodeEnd+1, previousNode.Pos(), previousNode.Pos(), MessageMissingWhitespaceAbove, cursor.checkType) } } @@ -475,7 +475,7 @@ func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { return } - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) + w.addErrorTooManyLines(stmt.Pos(), cursor.checkType) } func (w *WSL) CheckDeclStmt(stmt *ast.DeclStmt, cursor *Cursor) { @@ -491,7 +491,7 @@ func (w *WSL) CheckDeclStmt(stmt *ast.DeclStmt, cursor *Cursor) { return } - w.addError(stmt.End(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) + w.addErrorNeverAllow(stmt.Pos(), cursor.checkType) } func (w *WSL) CheckBlock(block *ast.BlockStmt) *Cursor { @@ -556,7 +556,7 @@ func (w *WSL) checkCaseTrailingNewline(body []ast.Stmt, cursor *Cursor) { return } - w.addError(lastStmt.End(), nextCase.Pos(), nextCase.Pos(), MessageAddWhitespace, CheckCaseTrailingNewline) + w.addError(lastStmt.End(), nextCase.Pos(), nextCase.Pos(), MessageMissingWhitespaceBelow, CheckCaseTrailingNewline) } func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { @@ -586,7 +586,7 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { return } - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) + w.addErrorTooManyLines(stmt.Pos(), cursor.checkType) } func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { @@ -631,7 +631,7 @@ func (w *WSL) CheckLabel(stmt *ast.LabeledStmt, cursor *Cursor) { return } - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) + w.addErrorNeverAllow(stmt.Pos(), cursor.checkType) } func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { @@ -664,7 +664,7 @@ func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { } if !hasIntersection(appendNode, previousNode) { - w.addError(stmt.Pos(), stmt.Pos(), stmt.Pos(), MessageAddWhitespace, cursor.checkType) + w.addErrorNoIntersection(stmt.Pos(), cursor.checkType) } } @@ -960,11 +960,41 @@ func (w *WSL) implementsErr(node *ast.Ident) bool { return types.Implements(typeInfo, errorType) } +func (w *WSL) addErrorInvalidTypeCuddle(pos token.Pos, ct CheckType) { + reportMessage := fmt.Sprintf("%s (invalid statement above %s)", MessageMissingWhitespaceAbove, ct) + w.addErrorWithMessage(pos, pos, pos, reportMessage) +} + +func (w *WSL) addErrorTooManyStatements(pos token.Pos, ct CheckType) { + reportMessage := fmt.Sprintf("%s (too many statements above %s)", MessageMissingWhitespaceAbove, ct) + w.addErrorWithMessage(pos, pos, pos, reportMessage) +} + +func (w *WSL) addErrorNoIntersection(pos token.Pos, ct CheckType) { + reportMessage := fmt.Sprintf("%s (no shared variables above %s)", MessageMissingWhitespaceAbove, ct) + w.addErrorWithMessage(pos, pos, pos, reportMessage) +} + +func (w *WSL) addErrorTooManyLines(pos token.Pos, ct CheckType) { + reportMessage := fmt.Sprintf("%s (too many lines above %s)", MessageMissingWhitespaceAbove, ct) + w.addErrorWithMessage(pos, pos, pos, reportMessage) +} + +func (w *WSL) addErrorNeverAllow(pos token.Pos, ct CheckType) { + reportMessage := fmt.Sprintf("%s (never cuddle %s)", MessageMissingWhitespaceAbove, ct) + w.addErrorWithMessage(pos, pos, pos, reportMessage) +} + func (w *WSL) addError(report, start, end token.Pos, message string, ct CheckType) { + reportMessage := fmt.Sprintf("%s (%s)", message, ct) + w.addErrorWithMessage(report, start, end, reportMessage) +} + +func (w *WSL) addErrorWithMessage(report, start, end token.Pos, message string) { issue, ok := w.Issues[report] if !ok { issue = Issue{ - Message: fmt.Sprintf("%s (%s)", message, ct), + Message: message, FixRanges: []FixRange{}, } } From f8526188f34304889eaabb1b7e5a243fd8c4bda2 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 17:08:58 +0200 Subject: [PATCH 086/112] Improve labeled statement handling --- testdata/src/default_config/label/label.go | 3 +-- testdata/src/default_config/label/label.go.golden | 3 +-- wsl.go | 10 +++++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/testdata/src/default_config/label/label.go b/testdata/src/default_config/label/label.go index 381f69e..9381472 100644 --- a/testdata/src/default_config/label/label.go +++ b/testdata/src/default_config/label/label.go @@ -23,13 +23,12 @@ func fn2() { } } -// TODO: L2 should be valid? func fn3() { L1: if true { _ = 1 } -L2: // want `missing whitespace above this line \(invalid statement above if\)` +L2: // want `missing whitespace above this line \(never cuddle label\)` if true { _ = 1 } diff --git a/testdata/src/default_config/label/label.go.golden b/testdata/src/default_config/label/label.go.golden index 99850af..9ef54e6 100644 --- a/testdata/src/default_config/label/label.go.golden +++ b/testdata/src/default_config/label/label.go.golden @@ -23,14 +23,13 @@ func fn2() { } } -// TODO: L2 should be valid? func fn3() { L1: if true { _ = 1 } -L2: // want `missing whitespace above this line \(invalid statement above if\)` +L2: // want `missing whitespace above this line \(never cuddle label\)` if true { _ = 1 } diff --git a/wsl.go b/wsl.go index 35572c7..01e6e33 100644 --- a/wsl.go +++ b/wsl.go @@ -619,7 +619,15 @@ func (w *WSL) CheckIncDec(stmt *ast.IncDecStmt, cursor *Cursor) { } func (w *WSL) CheckLabel(stmt *ast.LabeledStmt, cursor *Cursor) { - w.CheckStmt(stmt.Stmt, cursor) + // We check the statement last because the statement is the same node as the + // label (it's a labeled statement). This means that we _first_ want to + // check any violations of cuddling the label (never cuddle label) before we + // actually check the inner statement. + // + // It's a subtle difference, but it makes the diagnostic make more sense. + // We do this by deferring the statmenet check so it happens last no matter + // if we have label checking enabled or not. + defer w.CheckStmt(stmt.Stmt, cursor) if _, ok := w.Config.Checks[CheckLabel]; !ok { return From 19beb05b79da8c0800c5e935ebf13c099d04f4a8 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 17:17:49 +0200 Subject: [PATCH 087/112] Improve send handling --- testdata/src/with_config/send/send.go | 9 ++++++--- testdata/src/with_config/send/send.go.golden | 9 ++++++--- wsl.go | 16 ++++++++++++---- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/testdata/src/with_config/send/send.go b/testdata/src/with_config/send/send.go index 54c44ab..123d1b3 100644 --- a/testdata/src/with_config/send/send.go +++ b/testdata/src/with_config/send/send.go @@ -10,10 +10,13 @@ func fn1(ch chan int) { c := 1 ch <- 1 // want `missing whitespace above this line \(no shared variables above send\)` - // TODO: Technically this is used first in block but there's no easy way to - // figure out the first statement in the block so for now this is not valid. d := 2 ch <- func() int { // want `missing whitespace above this line \(no shared variables above send\)` - return d + return c + }() + + e := 2 + ch <- func() int { + return e }() } diff --git a/testdata/src/with_config/send/send.go.golden b/testdata/src/with_config/send/send.go.golden index 875719c..c538093 100644 --- a/testdata/src/with_config/send/send.go.golden +++ b/testdata/src/with_config/send/send.go.golden @@ -11,11 +11,14 @@ func fn1(ch chan int) { ch <- 1 // want `missing whitespace above this line \(no shared variables above send\)` - // TODO: Technically this is used first in block but there's no easy way to - // figure out the first statement in the block so for now this is not valid. d := 2 ch <- func() int { // want `missing whitespace above this line \(no shared variables above send\)` - return d + return c + }() + + e := 2 + ch <- func() int { + return e }() } diff --git a/wsl.go b/wsl.go index 01e6e33..9400a1d 100644 --- a/wsl.go +++ b/wsl.go @@ -381,9 +381,7 @@ func (w *WSL) CheckSelect(stmt *ast.SelectStmt, cursor *Cursor) { } func (w *WSL) CheckSend(stmt *ast.SendStmt, cursor *Cursor) { - defer func() { - w.CheckExpr(stmt.Value, cursor) - }() + defer w.CheckExpr(stmt.Value, cursor) if _, ok := w.Config.Checks[CheckSend]; !ok { return @@ -391,7 +389,17 @@ func (w *WSL) CheckSend(stmt *ast.SendStmt, cursor *Cursor) { cursor.SetChecker(CheckSend) - w.CheckCuddling(stmt, cursor, 1) + var stmts []ast.Stmt + ast.Inspect(stmt.Value, func(n ast.Node) bool { + if b, ok := n.(*ast.BlockStmt); ok { + stmts = b.List + return false + } + + return true + }) + + w.CheckCuddlingBlock(stmt, stmts, cursor, 1) } func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { From cc058e621826aa38e09f64c13b814143b7b47722 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 17:22:04 +0200 Subject: [PATCH 088/112] Add `default` as valid default, enable `send` by default --- README.md | 6 +++--- config.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0e4231c..b66d8ec 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ For more details and examples, see [RULES](RULES.md). lines where `n` is the value of [`branch-max-lines`](#configuration) - ✅ **select** - Select should only be cuddled with a single variable used on the line above -- ❌ **send** - Send should only be cuddled with a single variable used on the line +- ✅ **send** - Send should only be cuddled with a single variable used on the line above - ✅ **switch** - Switch should only be cuddled with a single variable used on the line above @@ -134,7 +134,7 @@ linters: allow-whole-block: false branch-max-lines: 2 case-max-lines: 0 - default: ~ # Can be `all`, `none` or empty + default: ~ # Can be `all`, `none`, `default` or empty enable: - assign - branch @@ -149,6 +149,7 @@ linters: - range - return - select + - send - switch - type-switch - append @@ -157,7 +158,6 @@ linters: disable: - assign-exclusive - err - - send ``` ## See also diff --git a/config.go b/config.go index dc4cd23..b9f70e3 100644 --- a/config.go +++ b/config.go @@ -158,7 +158,7 @@ func NewCheckSet( var cs CheckSet switch strings.ToLower(defaultChecks) { - case "": + case "", "default": cs = DefaultChecks() case "all": cs = AllChecks() @@ -207,6 +207,7 @@ func DefaultChecks() CheckSet { CheckRange: {}, CheckReturn: {}, CheckSelect: {}, + CheckSend: {}, CheckSwitch: {}, CheckTypeSwitch: {}, } @@ -216,7 +217,6 @@ func AllChecks() CheckSet { c := DefaultChecks() c.Add(CheckAssignExclusive) c.Add(CheckErr) - c.Add(CheckSend) return c } From 9b6cd999c73654a9303aef91637054169c6e5cff Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 17:23:13 +0200 Subject: [PATCH 089/112] Remove TODO comment --- testdata/src/default_config/go/go.go | 2 -- testdata/src/default_config/go/go.go.golden | 2 -- 2 files changed, 4 deletions(-) diff --git a/testdata/src/default_config/go/go.go b/testdata/src/default_config/go/go.go index 9a9c6b7..ef58324 100644 --- a/testdata/src/default_config/go/go.go +++ b/testdata/src/default_config/go/go.go @@ -48,8 +48,6 @@ func Go() { multiCuddle2 := NewT() // want `missing whitespace above this line \(too many statements above go\)` go multiCuddle2() - // TODO: Breaking change, this used to be on the first `go` stmt - now it's - // on the line that should have a blank line above. t4 := NewT() t5 := NewT() // want `missing whitespace above this line \(too many statements above go\)` go t5() diff --git a/testdata/src/default_config/go/go.go.golden b/testdata/src/default_config/go/go.go.golden index a1d9b7d..6b88425 100644 --- a/testdata/src/default_config/go/go.go.golden +++ b/testdata/src/default_config/go/go.go.golden @@ -53,8 +53,6 @@ func Go() { multiCuddle2 := NewT() // want `missing whitespace above this line \(too many statements above go\)` go multiCuddle2() - // TODO: Breaking change, this used to be on the first `go` stmt - now it's - // on the line that should have a blank line above. t4 := NewT() t5 := NewT() // want `missing whitespace above this line \(too many statements above go\)` From 36899db296a84b091426ce4b872774034dc1c27c Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 17:38:22 +0200 Subject: [PATCH 090/112] Remove config migration comment --- cmd/golangci-lint-migrate/main.go | 18 +++++++++++++----- config.go | 18 ------------------ 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/cmd/golangci-lint-migrate/main.go b/cmd/golangci-lint-migrate/main.go index 9bc7685..e966ac0 100644 --- a/cmd/golangci-lint-migrate/main.go +++ b/cmd/golangci-lint-migrate/main.go @@ -39,6 +39,19 @@ type NewConfig struct { } `yaml:"linters"` } +// StrictAppend is replaced with CheckAppend +// AllowAssignAndCall is in TODO for deprecation - needs investigation +// AllowAssignAndAnything is replaced with CheckAssign +// AllowMultilineAssign is deprecated and always allowed in v5 +// AllowTrailingComment is deprecated and always allowed in v5 +// AllowCuddleDeclarations is replaced with CheckDecl +// AllowSeparatedLeadingComment is deprecated and always allowed in v5 +// ForceErrCuddling is replaced with CheckErr +// ForceShortDeclCuddling is replaced with CheckAssignExclusive +// ForceCaseTrailingWhitespace is deprecated and replaced with CaseMaxLines +// AllowCuddlingWithCalls is deprecated and not needed in v5 +// AllowCuddleWithRHS is deprecated and not needed in v5 +// ErrorVariableNames is deprecated and not needed in v5 type WSL struct { StrictAppend bool `yaml:"strict-append"` AllowAssignAndCall bool `yaml:"allow-assign-and-call"` @@ -120,11 +133,6 @@ func main() { v5cfg.Enable = append(v5cfg.Enable, wsl.CheckAssignExclusive.String()) } - // These are deprecated and not needed in v5 - // v1cfg.AllowCuddlingWithCalls - // v1cfg.AllowCuddleWithRhs - // v1cfg.ErrorVariableNames - slices.Sort(v5cfg.Enable) slices.Sort(v5cfg.Disable) diff --git a/config.go b/config.go index b9f70e3..fd1ac7a 100644 --- a/config.go +++ b/config.go @@ -96,24 +96,6 @@ func (c CheckType) String() string { }[c] } -/* -Configuration migration - -- StrictAppend | Deprecate. Replaced with `CheckAppend` -- AllowAssignAndCallCuddle | TBD deprecate. Implemented, not configurable -- AllowAssignAndAnythingCuddle | Deprecated. Replaced with `CheckAssign` -- AllowMultiLineAssignCuddle | Deprecate. -- ForceCaseTrailingWhitespaceLimit | Done. -- AllowTrailingComment | TBD deprecate. Should be seen same as leading (allowed) -- AllowSeparatedLeadingComment | Deprecate. Always allowed. -- AllowCuddleDeclaration | Deprecate. Use `CheckDecl` instead. -- AllowCuddleWithCalls | TBD deprecate. Should not be needed. Was added to support mutex unlocking -- AllowCuddleWithRHS | TBD deprecate. Should not be needed. Not clear why separate from above -- ForceCuddleErrCheckAndAssign | Deprecate. Replaced with `CheckErr` -- ErrorVariableNames | Deprecate. We're now looking if the variable implements the error interface -- ForceExclusiveShortDeclarations | Done. -- IncludeGenerated | Done. -*/ type Configuration struct { IncludeGenerated bool AllowFirstInBlock bool From c5a24b48088914bfebc5df566542ab10eb09e5a0 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 18:15:30 +0200 Subject: [PATCH 091/112] Move `send` test --- README.md | 8 ++++---- testdata/src/default_config/assign/assign.go | 5 +++++ testdata/src/default_config/assign/assign.go.golden | 6 ++++++ testdata/src/{with_config => default_config}/send/send.go | 0 .../{with_config => default_config}/send/send.go.golden | 0 wsl_test.go | 6 ------ 6 files changed, 15 insertions(+), 10 deletions(-) rename testdata/src/{with_config => default_config}/send/send.go (100%) rename testdata/src/{with_config => default_config}/send/send.go.golden (100%) diff --git a/README.md b/README.md index b66d8ec..ca6430e 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,13 @@ For more details and examples, see [RULES](RULES.md). value of [`branch-max-lines`](#configuration) - ✅ **decl** - Declarations should never be cuddled - ✅ **defer** - Defer should only be cuddled with other `defer`, after error - checking or with variable used on the line above + checking or with a single variable used on the line above - ✅ **expr** - Expressions are e.g. function calls or index expressions, they should only be cuddled with variables used on the line above -- ✅ **for** - For loops should only be cuddled with a single variable used on the - line above -- ✅ **go** - Go should only be cuddled with other `go` or with variables used on +- ✅ **for** - For loops should only be cuddled with a single variable used on the line above +- ✅ **go** - Go should only be cuddled with other `go` or a single variable + used on the line above - ✅ **if** - If should only be cuddled with a single variable used on the line above - ✅ **inc-dec** - Increment/decrement (`++/--`) has the same rules as `assign` diff --git a/testdata/src/default_config/assign/assign.go b/testdata/src/default_config/assign/assign.go index 6393c17..2b560ef 100644 --- a/testdata/src/default_config/assign/assign.go +++ b/testdata/src/default_config/assign/assign.go @@ -64,3 +64,8 @@ func assignAfterBlock() { } x = 2 // want `missing whitespace above this line \(invalid statement above assign\)` } + +func decl() { + var x string + y := "" // want `missing whitespace above this line \(invalid statement above assign\)` +} diff --git a/testdata/src/default_config/assign/assign.go.golden b/testdata/src/default_config/assign/assign.go.golden index ef82370..8403a2e 100644 --- a/testdata/src/default_config/assign/assign.go.golden +++ b/testdata/src/default_config/assign/assign.go.golden @@ -66,3 +66,9 @@ func assignAfterBlock() { x = 2 // want `missing whitespace above this line \(invalid statement above assign\)` } + +func decl() { + var x string + + y := "" // want `missing whitespace above this line \(invalid statement above assign\)` +} diff --git a/testdata/src/with_config/send/send.go b/testdata/src/default_config/send/send.go similarity index 100% rename from testdata/src/with_config/send/send.go rename to testdata/src/default_config/send/send.go diff --git a/testdata/src/with_config/send/send.go.golden b/testdata/src/default_config/send/send.go.golden similarity index 100% rename from testdata/src/with_config/send/send.go.golden rename to testdata/src/default_config/send/send.go.golden diff --git a/wsl_test.go b/wsl_test.go index b346385..9b4ae15 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -73,12 +73,6 @@ func TestWithConfig(t *testing.T) { config.Checks.Add(CheckAssignExclusive) }, }, - { - subdir: "send", - configFn: func(config *Configuration) { - config.Checks.Add(CheckSend) - }, - }, { subdir: "disable_all", configFn: func(config *Configuration) { From e910a00fbe7cc6c5f24bfa87eea2c0a6ff7e74c2 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 18:29:23 +0200 Subject: [PATCH 092/112] Clarify cuddling without intersection --- wsl.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/wsl.go b/wsl.go index 9400a1d..edbb9df 100644 --- a/wsl.go +++ b/wsl.go @@ -161,20 +161,25 @@ func (w *WSL) checkCuddlingWithDecl( func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { previousNode := cursor.PreviousNode() + currAssign, currIsAssign := stmt.(*ast.AssignStmt) previousAssign, prevIsAssign := previousNode.(*ast.AssignStmt) _, prevIsDecl := previousNode.(*ast.DeclStmt) _, prevIsIncDec := previousNode.(*ast.IncDecStmt) - currAssign, currIsAssign := stmt.(*ast.AssignStmt) - // Most of the time we allow cuddling with declarations (var) if it's just - // one statement but not always so this can be disabled, e.g. for - // declarations themselves. + // Cuddling without intersection is allowed for assignments and inc/dec + // statements. If however the check for declarations is disabled, we also + // allow cuddling with them as well. + // + // var x string + // x := "" + // y++ if _, ok := w.Config.Checks[CheckDecl]; ok { prevIsDecl = false } // If we enable exclusive assign checks we only allow new declarations or // new assignments together but not mix and match. + // // When this is enabled we also implicitly disable support to cuddle with // anything else. if _, ok := w.Config.Checks[CheckAssignExclusive]; ok { From 47c19af71a1adea5941619ba33b2a0e69da6ab7d Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 18:41:01 +0200 Subject: [PATCH 093/112] Rename function --- wsl.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wsl.go b/wsl.go index edbb9df..d504dd8 100644 --- a/wsl.go +++ b/wsl.go @@ -62,14 +62,14 @@ func (w *WSL) CheckCuddlingBlock(stmt ast.Node, blockList []ast.Stmt, cursor *Cu firstBlockStmt = blockList[0] } - w.checkCuddlingWithDecl(stmt, firstBlockStmt, cursor, maxAllowedStatements) + w.checkCuddlingMaxAllowed(stmt, firstBlockStmt, cursor, maxAllowedStatements) } func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { - w.checkCuddlingWithDecl(stmt, nil, cursor, maxAllowedStatements) + w.checkCuddlingMaxAllowed(stmt, nil, cursor, maxAllowedStatements) } -func (w *WSL) checkCuddlingWithDecl( +func (w *WSL) checkCuddlingMaxAllowed( stmt ast.Node, firstBlockStmt ast.Node, cursor *Cursor, From 0ad28c2c691dd82c95d7055e6a8dc262cf1375b1 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 19:22:23 +0200 Subject: [PATCH 094/112] Fix copy-paste mistake --- RULES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RULES.md b/RULES.md index b5ca7b8..c6e5716 100644 --- a/RULES.md +++ b/RULES.md @@ -464,7 +464,7 @@ if xUsedFirstInBlock() { // Allowed with `allow-whole-block` x := 1 -if xUsedFirstInBlock() { +if xUsedLaterInBlock() { fmt.Println("will use x later") if orEvenNestedWouldWork() { From cfa164df5ffa8a9e97b1b66a78de314cbaa66ace Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 19:46:24 +0200 Subject: [PATCH 095/112] Make all wsl types and functions private --- README.md | 2 +- analyzer.go | 10 +- wsl.go | 1022 ++++++++++++++++++++++++++------------------------- 3 files changed, 518 insertions(+), 516 deletions(-) diff --git a/README.md b/README.md index ca6430e..ea2b408 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ linters: case-max-lines: 0 default: ~ # Can be `all`, `none`, `default` or empty enable: + - append - assign - branch - decl @@ -152,7 +153,6 @@ linters: - send - switch - type-switch - - append - leading-whitespace - trailing-whitespace disable: diff --git a/analyzer.go b/analyzer.go index 782fee1..248db23 100644 --- a/analyzer.go +++ b/analyzer.go @@ -110,13 +110,13 @@ func (wa *wslAnalyzer) run(pass *analysis.Pass) (any, error) { wsl := New(file, pass, wa.config) wsl.Run() - for pos, fix := range wsl.Issues { + for pos, fix := range wsl.issues { textEdits := []analysis.TextEdit{} - for _, f := range fix.FixRanges { + for _, f := range fix.fixRanges { textEdits = append(textEdits, analysis.TextEdit{ - Pos: f.FixRangeStart, - End: f.FixRangeEnd, + Pos: f.fixRangeStart, + End: f.fixRangeEnd, NewText: []byte("\n"), }) } @@ -124,7 +124,7 @@ func (wa *wslAnalyzer) run(pass *analysis.Pass) (any, error) { pass.Report(analysis.Diagnostic{ Pos: pos, Category: "whitespace", - Message: fix.Message, + Message: fix.message, SuggestedFixes: []analysis.SuggestedFix{ { TextEdits: textEdits, diff --git a/wsl.go b/wsl.go index d504dd8..635c137 100644 --- a/wsl.go +++ b/wsl.go @@ -11,52 +11,190 @@ import ( ) const ( - MessageMissingWhitespaceAbove = "missing whitespace above this line" - MessageMissingWhitespaceBelow = "missing whitespace below this line" - MessageRemoveWhitespace = "unnecessary whitespace" + messageMissingWhitespaceAbove = "missing whitespace above this line" + messageMissingWhitespaceBelow = "missing whitespace below this line" + messageRemoveWhitespace = "unnecessary whitespace" ) -type FixRange struct { - FixRangeStart token.Pos - FixRangeEnd token.Pos +type fixRange struct { + fixRangeStart token.Pos + fixRangeEnd token.Pos } -type Issue struct { - Message string +type issue struct { + message string // We can report multiple fixes at the same position. This happens e.g. when // we force error cuddling but the error assignment is already cuddled. // See `checkError` for examples. - FixRanges []FixRange + fixRanges []fixRange } type WSL struct { - File *ast.File - Fset *token.FileSet - TypeInfo *types.Info - Comments ast.CommentMap - Issues map[token.Pos]Issue - Config *Configuration + file *ast.File + fset *token.FileSet + typeInfo *types.Info + comments ast.CommentMap + issues map[token.Pos]issue + config *Configuration } func New(file *ast.File, pass *analysis.Pass, cfg *Configuration) *WSL { return &WSL{ - Fset: pass.Fset, - File: file, - TypeInfo: pass.TypesInfo, - Issues: make(map[token.Pos]Issue), - Config: cfg, + fset: pass.Fset, + file: file, + typeInfo: pass.TypesInfo, + issues: make(map[token.Pos]issue), + config: cfg, } } +// Run will run analysis on the file and pass passed to the constructor. It's +// typically only supposed to be used by [analysis.Analyzer]. func (w *WSL) Run() { - for _, decl := range w.File.Decls { + for _, decl := range w.file.Decls { if funcDecl, ok := decl.(*ast.FuncDecl); ok { - w.CheckFunc(funcDecl) + w.checkFunc(funcDecl) } } } -func (w *WSL) CheckCuddlingBlock(stmt ast.Node, blockList []ast.Stmt, cursor *Cursor, maxAllowedStatements int) { +func (w *WSL) checkStmt(stmt ast.Stmt, cursor *Cursor) { + //nolint:gocritic // This is not commented out code, it's examples + switch s := stmt.(type) { + // if a {} else if b {} else {} + case *ast.IfStmt: + w.checkIf(s, cursor, false) + // for {} / for a; b; c {} + case *ast.ForStmt: + w.checkFor(s, cursor) + // for _, _ = range a {} + case *ast.RangeStmt: + w.checkRange(s, cursor) + // switch {} // switch a {} + case *ast.SwitchStmt: + w.checkSwitch(s, cursor) + // switch a.(type) {} + case *ast.TypeSwitchStmt: + w.checkTypeSwitch(s, cursor) + // return a + case *ast.ReturnStmt: + w.checkReturn(s, cursor) + // continue / break + case *ast.BranchStmt: + w.checkBranch(s, cursor) + // var a + case *ast.DeclStmt: + w.checkDeclStmt(s, cursor) + // a := a + case *ast.AssignStmt: + w.checkAssign(s, cursor) + // a++ / a-- + case *ast.IncDecStmt: + w.checkIncDec(s, cursor) + // defer func() {} + case *ast.DeferStmt: + w.checkDefer(s, cursor) + // go func() {} + case *ast.GoStmt: + w.checkGo(s, cursor) + // e.g. someFn() + case *ast.ExprStmt: + w.checkExprStmt(s, cursor) + // case: + case *ast.CaseClause: + w.checkCaseClause(s, cursor) + // case: + case *ast.CommClause: + w.checkCommClause(s, cursor) + // { } + case *ast.BlockStmt: + w.checkBlock(s) + // select { } + case *ast.SelectStmt: + w.checkSelect(s, cursor) + // ch <- ... + case *ast.SendStmt: + w.checkSend(s, cursor) + // LABEL: + case *ast.LabeledStmt: + w.checkLabel(s, cursor) + case *ast.EmptyStmt: + default: + fmt.Printf("Not implemented stmt: %T\n", s) + } +} + +func (w *WSL) checkExpr(expr ast.Expr, cursor *Cursor) { + switch s := expr.(type) { + // func() {} + case *ast.FuncLit: + w.checkBlock(s.Body) + // Call(args...) + case *ast.CallExpr: + w.checkExpr(s.Fun, cursor) + + for _, e := range s.Args { + w.checkExpr(e, cursor) + } + case *ast.IndexExpr: + w.checkExpr(s.X, cursor) + case *ast.StarExpr: + w.checkExpr(s.X, cursor) + case *ast.ArrayType, + *ast.BasicLit, + *ast.BinaryExpr, + *ast.ChanType, + *ast.CompositeLit, + *ast.Ident, + *ast.MapType, + *ast.ParenExpr, + *ast.SelectorExpr, + *ast.SliceExpr, + *ast.TypeAssertExpr, + *ast.UnaryExpr, + nil: + default: + fmt.Printf("Not implemented expr: %T\n", s) + } +} + +func (w *WSL) checkDecl(decl ast.Decl, cursor *Cursor) { + switch d := decl.(type) { + case *ast.GenDecl: + for _, spec := range d.Specs { + w.checkSpec(spec, cursor) + } + case *ast.FuncDecl: + w.checkStmt(d.Body, cursor) + case *ast.BadDecl: + default: + fmt.Printf("Not implemented decl: %T\n", d) + } +} + +func (w *WSL) checkSpec(spec ast.Spec, cursor *Cursor) { + switch s := spec.(type) { + case *ast.ValueSpec: + for _, expr := range s.Values { + w.checkExpr(expr, cursor) + } + case *ast.ImportSpec, *ast.TypeSpec: + default: + fmt.Printf("Not implemented spec: %T\n", s) + } +} + +func (w *WSL) checkBody(body []ast.Stmt) *Cursor { + cursor := NewCursor(body) + + for cursor.Next() { + w.checkStmt(cursor.Stmt(), cursor) + } + + return cursor +} + +func (w *WSL) checkCuddlingBlock(stmt ast.Node, blockList []ast.Stmt, cursor *Cursor, maxAllowedStatements int) { var firstBlockStmt ast.Node if len(blockList) > 0 { firstBlockStmt = blockList[0] @@ -65,7 +203,7 @@ func (w *WSL) CheckCuddlingBlock(stmt ast.Node, blockList []ast.Stmt, cursor *Cu w.checkCuddlingMaxAllowed(stmt, firstBlockStmt, cursor, maxAllowedStatements) } -func (w *WSL) CheckCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { +func (w *WSL) checkCuddling(stmt ast.Node, cursor *Cursor, maxAllowedStatements int) { w.checkCuddlingMaxAllowed(stmt, nil, cursor, maxAllowedStatements) } @@ -128,7 +266,7 @@ func (w *WSL) checkCuddlingMaxAllowed( // FEATURE(AllowWholeBlock): Allow identifier used anywhere in block // (including recursive blocks). - if w.Config.AllowWholeBlock { + if w.config.AllowWholeBlock { allIdentsInBlock := identsFromNode(stmt, false) if checkIntersection(allIdentsInBlock) { return @@ -136,7 +274,7 @@ func (w *WSL) checkCuddlingMaxAllowed( } // FEATURE(AllowFirstInBlock): Allow identifiers used first in block. - if !w.Config.AllowWholeBlock && w.Config.AllowFirstInBlock { + if !w.config.AllowWholeBlock && w.config.AllowFirstInBlock { firstStmtIdents := identsFromNode(firstBlockStmt, true) if checkIntersection(firstStmtIdents) { return @@ -158,7 +296,7 @@ func (w *WSL) checkCuddlingMaxAllowed( w.addErrorNoIntersection(stmt.Pos(), cursor.checkType) } -func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { +func (w *WSL) checkCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { previousNode := cursor.PreviousNode() currAssign, currIsAssign := stmt.(*ast.AssignStmt) @@ -173,7 +311,7 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { // var x string // x := "" // y++ - if _, ok := w.Config.Checks[CheckDecl]; ok { + if _, ok := w.config.Checks[CheckDecl]; ok { prevIsDecl = false } @@ -182,7 +320,7 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { // // When this is enabled we also implicitly disable support to cuddle with // anything else. - if _, ok := w.Config.Checks[CheckAssignExclusive]; ok { + if _, ok := w.config.Checks[CheckAssignExclusive]; ok { prevIsDecl = false prevIsIncDec = false @@ -216,6 +354,164 @@ func (w *WSL) CheckCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { } } +func (w *WSL) checkBlock(block *ast.BlockStmt) *Cursor { + w.checkBlockLeadingNewline(block) + w.checkTrailingNewline(block) + + return w.checkBody(block.List) +} + +func (w *WSL) checkCaseClause(stmt *ast.CaseClause, cursor *Cursor) { + w.checkCaseLeadingNewline(stmt) + + if w.config.CaseMaxLines != 0 { + w.checkCaseTrailingNewline(stmt.Body, cursor) + } + + w.checkBody(stmt.Body) +} + +func (w *WSL) checkCommClause(stmt *ast.CommClause, cursor *Cursor) { + w.checkCommLeadingNewline(stmt) + + if w.config.CaseMaxLines != 0 { + w.checkCaseTrailingNewline(stmt.Body, cursor) + } + + w.checkBody(stmt.Body) +} + +func (w *WSL) checkFunc(funcDecl *ast.FuncDecl) { + if funcDecl.Body == nil { + return + } + + w.checkBlock(funcDecl.Body) +} + +func (w *WSL) checkAssign(stmt *ast.AssignStmt, cursor *Cursor) { + defer func() { + for _, expr := range stmt.Rhs { + w.checkExpr(expr, cursor) + } + }() + + if _, ok := w.config.Checks[CheckAssign]; !ok { + return + } + + cursor.SetChecker(CheckAssign) + + w.checkCuddlingWithoutIntersection(stmt, cursor) + w.checkAppend(stmt, cursor) +} + +func (w *WSL) checkAppend(stmt *ast.AssignStmt, cursor *Cursor) { + if _, ok := w.config.Checks[CheckAppend]; !ok { + return + } + + if w.numberOfStatementsAbove(cursor) == 0 { + return + } + + previousNode := cursor.PreviousNode() + + var appendNode *ast.CallExpr + + for _, expr := range stmt.Rhs { + e, ok := expr.(*ast.CallExpr) + if !ok { + continue + } + + if f, ok := e.Fun.(*ast.Ident); ok && f.Name == "append" { + appendNode = e + break + } + } + + if appendNode == nil { + return + } + + if !hasIntersection(appendNode, previousNode) { + w.addErrorNoIntersection(stmt.Pos(), cursor.checkType) + } +} + +func (w *WSL) checkBranch(stmt *ast.BranchStmt, cursor *Cursor) { + if _, ok := w.config.Checks[CheckBranch]; !ok { + return + } + + cursor.SetChecker(CheckBranch) + + if w.numberOfStatementsAbove(cursor) == 0 { + return + } + + lastStmtInBlock := cursor.statements[len(cursor.statements)-1] + firstStmts := cursor.Nth(0) + + if w.lineFor(lastStmtInBlock.End())-w.lineFor(firstStmts.Pos()) < w.config.BranchMaxLines { + return + } + + w.addErrorTooManyLines(stmt.Pos(), cursor.checkType) +} + +func (w *WSL) checkDeclStmt(stmt *ast.DeclStmt, cursor *Cursor) { + w.checkDecl(stmt.Decl, cursor) + + if _, ok := w.config.Checks[CheckDecl]; !ok { + return + } + + cursor.SetChecker(CheckDecl) + + if w.numberOfStatementsAbove(cursor) == 0 { + return + } + + w.addErrorNeverAllow(stmt.Pos(), cursor.checkType) +} + +func (w *WSL) checkDefer(stmt *ast.DeferStmt, cursor *Cursor) { + w.maybeCheckExpr( + stmt, + stmt.Call, + cursor, + func(n ast.Node) (int, bool) { + _, previousIsDefer := n.(*ast.DeferStmt) + _, previousIsIf := n.(*ast.IfStmt) + + // We allow defer as a third node only if we have an if statement + // between, e.g. + // + // f, err := os.Open(file) + // if err != nil { + // return err + // } + // defer f.Close() + if previousIsIf && w.numberOfStatementsAbove(cursor) >= 2 { + defer cursor.Save()() + + cursor.Previous() + cursor.Previous() + + if hasIntersection(cursor.Stmt(), stmt) { + return 1, false + } + } + + // Only check cuddling if previous statement isn't also a defer. + return 1, !previousIsDefer + }, + CheckDefer, + ) +} + func (w *WSL) checkError( stmtsAbove int, ifStmt ast.Node, @@ -229,7 +525,7 @@ func (w *WSL) checkError( return } - if _, ok := w.Config.Checks[CheckErr]; !ok { + if _, ok := w.config.Checks[CheckErr]; !ok { return } @@ -247,7 +543,7 @@ func (w *WSL) checkError( previousNodeEnd := previousNode.End() - comments := ast.NewCommentMap(w.Fset, previousNode, w.File.Comments) + comments := ast.NewCommentMap(w.fset, previousNode, w.file.Comments) for _, cg := range comments { for _, c := range cg { if c.Pos() < previousNodeEnd || c.End() > ifStmt.Pos() { @@ -277,7 +573,7 @@ func (w *WSL) checkError( } } - w.addError(previousNodeEnd+1, previousNodeEnd, ifStmt.Pos(), MessageRemoveWhitespace, cursor.checkType) + w.addError(previousNodeEnd+1, previousNodeEnd, ifStmt.Pos(), messageRemoveWhitespace, cursor.checkType) // If we add the error at the same position but with a different fix // range, only the fix range will be updated. @@ -300,128 +596,29 @@ func (w *WSL) checkError( // diagnostic, the issue isn't present until the first fix so this message // will never be shown to the user. if w.numberOfStatementsAbove(cursor) > 0 { - w.addError(previousNodeEnd+1, previousNode.Pos(), previousNode.Pos(), MessageMissingWhitespaceAbove, cursor.checkType) + w.addError(previousNodeEnd+1, previousNode.Pos(), previousNode.Pos(), messageMissingWhitespaceAbove, cursor.checkType) } } -func (w *WSL) MaybeCheckBlock( - node ast.Node, - blockStmt *ast.BlockStmt, - cursor *Cursor, - check CheckType, -) { - w.CheckBlock(blockStmt) +func (w *WSL) checkExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { + w.maybeCheckExpr( + stmt, + stmt.X, + cursor, + func(n ast.Node) (int, bool) { + _, ok := n.(*ast.ExprStmt) + return -1, !ok + }, + CheckExpr, + ) +} - if _, ok := w.Config.Checks[check]; ok { - cursor.SetChecker(check) - w.CheckCuddlingBlock(node, blockStmt.List, cursor, 1) - } +func (w *WSL) checkFor(stmt *ast.ForStmt, cursor *Cursor) { + w.maybeCheckBlock(stmt, stmt.Body, cursor, CheckFor) } -func (w *WSL) MaybeCheckExpr( - node ast.Node, - expr ast.Expr, - cursor *Cursor, - predicate func(ast.Node) (int, bool), - check CheckType, -) { - w.CheckExpr(expr, cursor) - - if _, ok := w.Config.Checks[check]; ok { - cursor.SetChecker(check) - previousNode := cursor.PreviousNode() - - if n, ok := predicate(previousNode); ok { - w.CheckCuddling(node, cursor, n) - } - } -} - -func (w *WSL) CheckFunc(funcDecl *ast.FuncDecl) { - if funcDecl.Body == nil { - return - } - - w.CheckBlock(funcDecl.Body) -} - -func (w *WSL) CheckIf(stmt *ast.IfStmt, cursor *Cursor, isElse bool) { - // if - w.CheckBlock(stmt.Body) - - switch v := stmt.Else.(type) { - // else-if - case *ast.IfStmt: - w.CheckIf(v, cursor, true) - - // else - case *ast.BlockStmt: - w.CheckBlock(v) - } - - if _, ok := w.Config.Checks[CheckIf]; !isElse && ok { - cursor.SetChecker(CheckIf) - w.CheckCuddlingBlock(stmt, stmt.Body.List, cursor, 1) - } -} - -func (w *WSL) CheckFor(stmt *ast.ForStmt, cursor *Cursor) { - w.MaybeCheckBlock(stmt, stmt.Body, cursor, CheckFor) -} - -func (w *WSL) CheckRange(stmt *ast.RangeStmt, cursor *Cursor) { - w.MaybeCheckBlock(stmt, stmt.Body, cursor, CheckRange) -} - -func (w *WSL) CheckSwitch(stmt *ast.SwitchStmt, cursor *Cursor) { - w.MaybeCheckBlock(stmt, stmt.Body, cursor, CheckSwitch) -} - -func (w *WSL) CheckTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { - w.MaybeCheckBlock(stmt, stmt.Body, cursor, CheckTypeSwitch) -} - -func (w *WSL) CheckSelect(stmt *ast.SelectStmt, cursor *Cursor) { - w.MaybeCheckBlock(stmt, stmt.Body, cursor, CheckSelect) -} - -func (w *WSL) CheckSend(stmt *ast.SendStmt, cursor *Cursor) { - defer w.CheckExpr(stmt.Value, cursor) - - if _, ok := w.Config.Checks[CheckSend]; !ok { - return - } - - cursor.SetChecker(CheckSend) - - var stmts []ast.Stmt - ast.Inspect(stmt.Value, func(n ast.Node) bool { - if b, ok := n.(*ast.BlockStmt); ok { - stmts = b.List - return false - } - - return true - }) - - w.CheckCuddlingBlock(stmt, stmts, cursor, 1) -} - -func (w *WSL) CheckExprStmt(stmt *ast.ExprStmt, cursor *Cursor) { - w.MaybeCheckExpr( - stmt, - stmt.X, - cursor, - func(n ast.Node) (int, bool) { - _, ok := n.(*ast.ExprStmt) - return -1, !ok - }, - CheckExpr, - ) -} - -func (w *WSL) CheckGo(stmt *ast.GoStmt, cursor *Cursor) { - w.MaybeCheckExpr( +func (w *WSL) checkGo(stmt *ast.GoStmt, cursor *Cursor) { + w.maybeCheckExpr( stmt, stmt.Call, cursor, @@ -435,70 +632,54 @@ func (w *WSL) CheckGo(stmt *ast.GoStmt, cursor *Cursor) { ) } -func (w *WSL) CheckDefer(stmt *ast.DeferStmt, cursor *Cursor) { - w.MaybeCheckExpr( - stmt, - stmt.Call, - cursor, - func(n ast.Node) (int, bool) { - _, previousIsDefer := n.(*ast.DeferStmt) - _, previousIsIf := n.(*ast.IfStmt) - - // We allow defer as a third node only if we have an if statement - // between, e.g. - // - // f, err := os.Open(file) - // if err != nil { - // return err - // } - // defer f.Close() - if previousIsIf && w.numberOfStatementsAbove(cursor) >= 2 { - defer cursor.Save()() - - cursor.Previous() - cursor.Previous() - - if hasIntersection(cursor.Stmt(), stmt) { - return 1, false - } - } +func (w *WSL) checkIf(stmt *ast.IfStmt, cursor *Cursor, isElse bool) { + // if + w.checkBlock(stmt.Body) - // Only check cuddling if previous statement isn't also a defer. - return 1, !previousIsDefer - }, - CheckDefer, - ) -} + switch v := stmt.Else.(type) { + // else-if + case *ast.IfStmt: + w.checkIf(v, cursor, true) -func (w *WSL) CheckBranch(stmt *ast.BranchStmt, cursor *Cursor) { - if _, ok := w.Config.Checks[CheckBranch]; !ok { - return + // else + case *ast.BlockStmt: + w.checkBlock(v) } - cursor.SetChecker(CheckBranch) - - if w.numberOfStatementsAbove(cursor) == 0 { - return + if _, ok := w.config.Checks[CheckIf]; !isElse && ok { + cursor.SetChecker(CheckIf) + w.checkCuddlingBlock(stmt, stmt.Body.List, cursor, 1) } +} - lastStmtInBlock := cursor.statements[len(cursor.statements)-1] - firstStmts := cursor.Nth(0) +func (w *WSL) checkIncDec(stmt *ast.IncDecStmt, cursor *Cursor) { + defer w.checkExpr(stmt.X, cursor) - if w.lineFor(lastStmtInBlock.End())-w.lineFor(firstStmts.Pos()) < w.Config.BranchMaxLines { + if _, ok := w.config.Checks[CheckIncDec]; !ok { return } - w.addErrorTooManyLines(stmt.Pos(), cursor.checkType) + cursor.SetChecker(CheckIncDec) + + w.checkCuddlingWithoutIntersection(stmt, cursor) } -func (w *WSL) CheckDeclStmt(stmt *ast.DeclStmt, cursor *Cursor) { - w.CheckDecl(stmt.Decl, cursor) +func (w *WSL) checkLabel(stmt *ast.LabeledStmt, cursor *Cursor) { + // We check the statement last because the statement is the same node as the + // label (it's a labeled statement). This means that we _first_ want to + // check any violations of cuddling the label (never cuddle label) before we + // actually check the inner statement. + // + // It's a subtle difference, but it makes the diagnostic make more sense. + // We do this by deferring the statmenet check so it happens last no matter + // if we have label checking enabled or not. + defer w.checkStmt(stmt.Stmt, cursor) - if _, ok := w.Config.Checks[CheckDecl]; !ok { + if _, ok := w.config.Checks[CheckLabel]; !ok { return } - cursor.SetChecker(CheckDecl) + cursor.SetChecker(CheckLabel) if w.numberOfStatementsAbove(cursor) == 0 { return @@ -507,77 +688,16 @@ func (w *WSL) CheckDeclStmt(stmt *ast.DeclStmt, cursor *Cursor) { w.addErrorNeverAllow(stmt.Pos(), cursor.checkType) } -func (w *WSL) CheckBlock(block *ast.BlockStmt) *Cursor { - w.CheckBlockLeadingNewline(block) - w.CheckTrailingNewline(block) - - return w.checkBody(block.List) +func (w *WSL) checkRange(stmt *ast.RangeStmt, cursor *Cursor) { + w.maybeCheckBlock(stmt, stmt.Body, cursor, CheckRange) } -func (w *WSL) CheckCase(stmt *ast.CaseClause, cursor *Cursor) { - w.CheckCaseLeadingNewline(stmt) - - if w.Config.CaseMaxLines != 0 { - w.checkCaseTrailingNewline(stmt.Body, cursor) - } - - w.checkBody(stmt.Body) -} - -func (w *WSL) CheckComm(stmt *ast.CommClause, cursor *Cursor) { - w.CheckCommLeadingNewline(stmt) - - if w.Config.CaseMaxLines != 0 { - w.checkCaseTrailingNewline(stmt.Body, cursor) - } - - w.checkBody(stmt.Body) -} - -func (w *WSL) checkCaseTrailingNewline(body []ast.Stmt, cursor *Cursor) { - if len(body) == 0 { - return - } - - defer cursor.Save()() - - if !cursor.Next() { - return - } - - var nextCase ast.Node - switch n := cursor.Stmt().(type) { - case *ast.CaseClause: - nextCase = n - case *ast.CommClause: - nextCase = n - default: - return - } - - firstStmt := body[0] - lastStmt := body[len(body)-1] - totalLines := w.lineFor(lastStmt.End()) - w.lineFor(firstStmt.Pos()) + 1 - - if totalLines < w.Config.CaseMaxLines { - return - } - - // Next case is not immediately after the last statement so must be newline - // already. - if w.lineFor(nextCase.Pos()) > w.lineFor(lastStmt.End())+1 { - return - } - - w.addError(lastStmt.End(), nextCase.Pos(), nextCase.Pos(), MessageMissingWhitespaceBelow, CheckCaseTrailingNewline) -} - -func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { +func (w *WSL) checkReturn(stmt *ast.ReturnStmt, cursor *Cursor) { for _, expr := range stmt.Results { - w.CheckExpr(expr, cursor) + w.checkExpr(expr, cursor) } - if _, ok := w.Config.Checks[CheckReturn]; !ok { + if _, ok := w.config.Checks[CheckReturn]; !ok { return } @@ -595,274 +715,102 @@ func (w *WSL) CheckReturn(stmt *ast.ReturnStmt, cursor *Cursor) { // If the distance between the first statement and the return statement is // less than `n` LOC we're allowed to cuddle. firstStmts := cursor.Nth(0) - if w.lineFor(stmt.End())-w.lineFor(firstStmts.Pos()) < w.Config.BranchMaxLines { + if w.lineFor(stmt.End())-w.lineFor(firstStmts.Pos()) < w.config.BranchMaxLines { return } w.addErrorTooManyLines(stmt.Pos(), cursor.checkType) } -func (w *WSL) CheckAssign(stmt *ast.AssignStmt, cursor *Cursor) { - defer func() { - for _, expr := range stmt.Rhs { - w.CheckExpr(expr, cursor) - } - }() - - if _, ok := w.Config.Checks[CheckAssign]; !ok { - return - } - - cursor.SetChecker(CheckAssign) - - w.CheckCuddlingWithoutIntersection(stmt, cursor) - w.strictAppendCheck(stmt, cursor) +func (w *WSL) checkSelect(stmt *ast.SelectStmt, cursor *Cursor) { + w.maybeCheckBlock(stmt, stmt.Body, cursor, CheckSelect) } -func (w *WSL) CheckIncDec(stmt *ast.IncDecStmt, cursor *Cursor) { - defer w.CheckExpr(stmt.X, cursor) +func (w *WSL) checkSend(stmt *ast.SendStmt, cursor *Cursor) { + defer w.checkExpr(stmt.Value, cursor) - if _, ok := w.Config.Checks[CheckIncDec]; !ok { + if _, ok := w.config.Checks[CheckSend]; !ok { return } - cursor.SetChecker(CheckIncDec) - - w.CheckCuddlingWithoutIntersection(stmt, cursor) -} - -func (w *WSL) CheckLabel(stmt *ast.LabeledStmt, cursor *Cursor) { - // We check the statement last because the statement is the same node as the - // label (it's a labeled statement). This means that we _first_ want to - // check any violations of cuddling the label (never cuddle label) before we - // actually check the inner statement. - // - // It's a subtle difference, but it makes the diagnostic make more sense. - // We do this by deferring the statmenet check so it happens last no matter - // if we have label checking enabled or not. - defer w.CheckStmt(stmt.Stmt, cursor) + cursor.SetChecker(CheckSend) - if _, ok := w.Config.Checks[CheckLabel]; !ok { - return - } + var stmts []ast.Stmt + ast.Inspect(stmt.Value, func(n ast.Node) bool { + if b, ok := n.(*ast.BlockStmt); ok { + stmts = b.List + return false + } - cursor.SetChecker(CheckLabel) + return true + }) - if w.numberOfStatementsAbove(cursor) == 0 { - return - } + w.checkCuddlingBlock(stmt, stmts, cursor, 1) +} - w.addErrorNeverAllow(stmt.Pos(), cursor.checkType) +func (w *WSL) checkSwitch(stmt *ast.SwitchStmt, cursor *Cursor) { + w.maybeCheckBlock(stmt, stmt.Body, cursor, CheckSwitch) } -func (w *WSL) strictAppendCheck(stmt *ast.AssignStmt, cursor *Cursor) { - if _, ok := w.Config.Checks[CheckAppend]; !ok { - return - } +func (w *WSL) checkTypeSwitch(stmt *ast.TypeSwitchStmt, cursor *Cursor) { + w.maybeCheckBlock(stmt, stmt.Body, cursor, CheckTypeSwitch) +} - if w.numberOfStatementsAbove(cursor) == 0 { +func (w *WSL) checkCaseTrailingNewline(body []ast.Stmt, cursor *Cursor) { + if len(body) == 0 { return } - previousNode := cursor.PreviousNode() - - var appendNode *ast.CallExpr - - for _, expr := range stmt.Rhs { - e, ok := expr.(*ast.CallExpr) - if !ok { - continue - } - - if f, ok := e.Fun.(*ast.Ident); ok && f.Name == "append" { - appendNode = e - break - } - } + defer cursor.Save()() - if appendNode == nil { + if !cursor.Next() { return } - if !hasIntersection(appendNode, previousNode) { - w.addErrorNoIntersection(stmt.Pos(), cursor.checkType) - } -} - -func (w *WSL) checkBody(body []ast.Stmt) *Cursor { - cursor := NewCursor(body) - - for cursor.Next() { - w.CheckStmt(cursor.Stmt(), cursor) - } - - return cursor -} - -func (w *WSL) CheckStmt(stmt ast.Stmt, cursor *Cursor) { - //nolint:gocritic // This is not commented out code, it's examples - switch s := stmt.(type) { - // if a {} else if b {} else {} - case *ast.IfStmt: - w.CheckIf(s, cursor, false) - // for {} / for a; b; c {} - case *ast.ForStmt: - w.CheckFor(s, cursor) - // for _, _ = range a {} - case *ast.RangeStmt: - w.CheckRange(s, cursor) - // switch {} // switch a {} - case *ast.SwitchStmt: - w.CheckSwitch(s, cursor) - // switch a.(type) {} - case *ast.TypeSwitchStmt: - w.CheckTypeSwitch(s, cursor) - // return a - case *ast.ReturnStmt: - w.CheckReturn(s, cursor) - // continue / break - case *ast.BranchStmt: - w.CheckBranch(s, cursor) - // var a - case *ast.DeclStmt: - w.CheckDeclStmt(s, cursor) - // a := a - case *ast.AssignStmt: - w.CheckAssign(s, cursor) - // a++ / a-- - case *ast.IncDecStmt: - w.CheckIncDec(s, cursor) - // defer func() {} - case *ast.DeferStmt: - w.CheckDefer(s, cursor) - // go func() {} - case *ast.GoStmt: - w.CheckGo(s, cursor) - // e.g. someFn() - case *ast.ExprStmt: - w.CheckExprStmt(s, cursor) - // case: + var nextCase ast.Node + switch n := cursor.Stmt().(type) { case *ast.CaseClause: - w.CheckCase(s, cursor) - // case: + nextCase = n case *ast.CommClause: - w.CheckComm(s, cursor) - // { } - case *ast.BlockStmt: - w.CheckBlock(s) - // select { } - case *ast.SelectStmt: - w.CheckSelect(s, cursor) - // ch <- ... - case *ast.SendStmt: - w.CheckSend(s, cursor) - // LABEL: - case *ast.LabeledStmt: - w.CheckLabel(s, cursor) - case *ast.EmptyStmt: - default: - fmt.Printf("Not implemented stmt: %T\n", s) - } -} - -func (w *WSL) CheckExpr(expr ast.Expr, cursor *Cursor) { - switch s := expr.(type) { - // func() {} - case *ast.FuncLit: - w.CheckBlock(s.Body) - // Call(args...) - case *ast.CallExpr: - w.CheckExpr(s.Fun, cursor) - - for _, e := range s.Args { - w.CheckExpr(e, cursor) - } - case *ast.IndexExpr: - w.CheckExpr(s.X, cursor) - case *ast.StarExpr: - w.CheckExpr(s.X, cursor) - case *ast.ArrayType, - *ast.BasicLit, - *ast.BinaryExpr, - *ast.ChanType, - *ast.CompositeLit, - *ast.Ident, - *ast.MapType, - *ast.ParenExpr, - *ast.SelectorExpr, - *ast.SliceExpr, - *ast.TypeAssertExpr, - *ast.UnaryExpr, - nil: + nextCase = n default: - fmt.Printf("Not implemented expr: %T\n", s) + return } -} -func (w *WSL) CheckDecl(decl ast.Decl, cursor *Cursor) { - switch d := decl.(type) { - case *ast.GenDecl: - for _, spec := range d.Specs { - w.CheckSpec(spec, cursor) - } - case *ast.FuncDecl: - w.CheckStmt(d.Body, cursor) - case *ast.BadDecl: - default: - fmt.Printf("Not implemented decl: %T\n", d) - } -} + firstStmt := body[0] + lastStmt := body[len(body)-1] + totalLines := w.lineFor(lastStmt.End()) - w.lineFor(firstStmt.Pos()) + 1 -func (w *WSL) CheckSpec(spec ast.Spec, cursor *Cursor) { - switch s := spec.(type) { - case *ast.ValueSpec: - for _, expr := range s.Values { - w.CheckExpr(expr, cursor) - } - case *ast.ImportSpec, *ast.TypeSpec: - default: - fmt.Printf("Not implemented spec: %T\n", s) + if totalLines < w.config.CaseMaxLines { + return } -} - -// numberOfStatementsAbove will find out how many lines above the cursor's -// current statement there is without any newlines between. -func (w *WSL) numberOfStatementsAbove(cursor *Cursor) int { - defer cursor.Save()() - statementsWithoutNewlines := 0 - currentStmtStartLine := w.lineFor(cursor.Stmt().Pos()) - - for cursor.Previous() { - previousStmtEndLine := w.lineFor(cursor.Stmt().End()) - if previousStmtEndLine != currentStmtStartLine-1 { - break - } - - currentStmtStartLine = w.lineFor(cursor.Stmt().Pos()) - statementsWithoutNewlines++ + // Next case is not immediately after the last statement so must be newline + // already. + if w.lineFor(nextCase.Pos()) > w.lineFor(lastStmt.End())+1 { + return } - return statementsWithoutNewlines + w.addError(lastStmt.End(), nextCase.Pos(), nextCase.Pos(), messageMissingWhitespaceBelow, CheckCaseTrailingNewline) } -func (w *WSL) CheckBlockLeadingNewline(body *ast.BlockStmt) { - comments := ast.NewCommentMap(w.Fset, body, w.File.Comments) - w.CheckLeadingNewline(body.Lbrace, body.List, comments) +func (w *WSL) checkBlockLeadingNewline(body *ast.BlockStmt) { + comments := ast.NewCommentMap(w.fset, body, w.file.Comments) + w.checkLeadingNewline(body.Lbrace, body.List, comments) } -func (w *WSL) CheckCaseLeadingNewline(caseClause *ast.CaseClause) { - comments := ast.NewCommentMap(w.Fset, caseClause, w.File.Comments) - w.CheckLeadingNewline(caseClause.Colon, caseClause.Body, comments) +func (w *WSL) checkCaseLeadingNewline(caseClause *ast.CaseClause) { + comments := ast.NewCommentMap(w.fset, caseClause, w.file.Comments) + w.checkLeadingNewline(caseClause.Colon, caseClause.Body, comments) } -func (w *WSL) CheckCommLeadingNewline(commClause *ast.CommClause) { - comments := ast.NewCommentMap(w.Fset, commClause, w.File.Comments) - w.CheckLeadingNewline(commClause.Colon, commClause.Body, comments) +func (w *WSL) checkCommLeadingNewline(commClause *ast.CommClause) { + comments := ast.NewCommentMap(w.fset, commClause, w.file.Comments) + w.checkLeadingNewline(commClause.Colon, commClause.Body, comments) } -func (w *WSL) CheckLeadingNewline(startPos token.Pos, body []ast.Stmt, comments ast.CommentMap) { - if _, ok := w.Config.Checks[CheckLeadingWhitespace]; !ok { +func (w *WSL) checkLeadingNewline(startPos token.Pos, body []ast.Stmt, comments ast.CommentMap) { + if _, ok := w.config.Checks[CheckLeadingWhitespace]; !ok { return } @@ -910,16 +858,16 @@ func (w *WSL) CheckLeadingNewline(startPos token.Pos, body []ast.Stmt, comments } } - openingPosLine := w.Fset.PositionFor(openingPos, false).Line - firstStmtLine := w.Fset.PositionFor(firstStmt, false).Line + openingPosLine := w.fset.PositionFor(openingPos, false).Line + firstStmtLine := w.fset.PositionFor(firstStmt, false).Line if firstStmtLine > openingPosLine+1 { - w.addError(openingPos+1, openingPos, firstStmt, MessageRemoveWhitespace, CheckLeadingWhitespace) + w.addError(openingPos+1, openingPos, firstStmt, messageRemoveWhitespace, CheckLeadingWhitespace) } } -func (w *WSL) CheckTrailingNewline(body *ast.BlockStmt) { - if _, ok := w.Config.Checks[CheckTrailingWhitespace]; !ok { +func (w *WSL) checkTrailingNewline(body *ast.BlockStmt) { + if _, ok := w.config.Checks[CheckTrailingWhitespace]; !ok { return } @@ -946,7 +894,7 @@ func (w *WSL) CheckTrailingNewline(body *ast.BlockStmt) { } } - comments := ast.NewCommentMap(w.Fset, body, w.File.Comments) + comments := ast.NewCommentMap(w.fset, body, w.file.Comments) for _, commentGroup := range comments { for _, comment := range commentGroup { if comment.End() < closingPos && comment.Pos() > lastStmtOrComment { @@ -955,20 +903,74 @@ func (w *WSL) CheckTrailingNewline(body *ast.BlockStmt) { } } - closingPosLine := w.Fset.PositionFor(closingPos, false).Line - lastStmtLine := w.Fset.PositionFor(lastStmtOrComment, false).Line + closingPosLine := w.fset.PositionFor(closingPos, false).Line + lastStmtLine := w.fset.PositionFor(lastStmtOrComment, false).Line if closingPosLine > lastStmtLine+1 { - w.addError(lastStmtOrComment+1, lastStmtOrComment, closingPos, MessageRemoveWhitespace, CheckTrailingWhitespace) + w.addError(lastStmtOrComment+1, lastStmtOrComment, closingPos, messageRemoveWhitespace, CheckTrailingWhitespace) + } +} + +func (w *WSL) maybeCheckBlock( + node ast.Node, + blockStmt *ast.BlockStmt, + cursor *Cursor, + check CheckType, +) { + w.checkBlock(blockStmt) + + if _, ok := w.config.Checks[check]; ok { + cursor.SetChecker(check) + w.checkCuddlingBlock(node, blockStmt.List, cursor, 1) + } +} + +func (w *WSL) maybeCheckExpr( + node ast.Node, + expr ast.Expr, + cursor *Cursor, + predicate func(ast.Node) (int, bool), + check CheckType, +) { + w.checkExpr(expr, cursor) + + if _, ok := w.config.Checks[check]; ok { + cursor.SetChecker(check) + previousNode := cursor.PreviousNode() + + if n, ok := predicate(previousNode); ok { + w.checkCuddling(node, cursor, n) + } + } +} + +// numberOfStatementsAbove will find out how many lines above the cursor's +// current statement there is without any newlines between. +func (w *WSL) numberOfStatementsAbove(cursor *Cursor) int { + defer cursor.Save()() + + statementsWithoutNewlines := 0 + currentStmtStartLine := w.lineFor(cursor.Stmt().Pos()) + + for cursor.Previous() { + previousStmtEndLine := w.lineFor(cursor.Stmt().End()) + if previousStmtEndLine != currentStmtStartLine-1 { + break + } + + currentStmtStartLine = w.lineFor(cursor.Stmt().Pos()) + statementsWithoutNewlines++ } + + return statementsWithoutNewlines } func (w *WSL) lineFor(pos token.Pos) int { - return w.Fset.PositionFor(pos, false).Line + return w.fset.PositionFor(pos, false).Line } func (w *WSL) implementsErr(node *ast.Ident) bool { - typeInfo := w.TypeInfo.TypeOf(node) + typeInfo := w.typeInfo.TypeOf(node) if typeInfo == nil { return false } @@ -982,27 +984,27 @@ func (w *WSL) implementsErr(node *ast.Ident) bool { } func (w *WSL) addErrorInvalidTypeCuddle(pos token.Pos, ct CheckType) { - reportMessage := fmt.Sprintf("%s (invalid statement above %s)", MessageMissingWhitespaceAbove, ct) + reportMessage := fmt.Sprintf("%s (invalid statement above %s)", messageMissingWhitespaceAbove, ct) w.addErrorWithMessage(pos, pos, pos, reportMessage) } func (w *WSL) addErrorTooManyStatements(pos token.Pos, ct CheckType) { - reportMessage := fmt.Sprintf("%s (too many statements above %s)", MessageMissingWhitespaceAbove, ct) + reportMessage := fmt.Sprintf("%s (too many statements above %s)", messageMissingWhitespaceAbove, ct) w.addErrorWithMessage(pos, pos, pos, reportMessage) } func (w *WSL) addErrorNoIntersection(pos token.Pos, ct CheckType) { - reportMessage := fmt.Sprintf("%s (no shared variables above %s)", MessageMissingWhitespaceAbove, ct) + reportMessage := fmt.Sprintf("%s (no shared variables above %s)", messageMissingWhitespaceAbove, ct) w.addErrorWithMessage(pos, pos, pos, reportMessage) } func (w *WSL) addErrorTooManyLines(pos token.Pos, ct CheckType) { - reportMessage := fmt.Sprintf("%s (too many lines above %s)", MessageMissingWhitespaceAbove, ct) + reportMessage := fmt.Sprintf("%s (too many lines above %s)", messageMissingWhitespaceAbove, ct) w.addErrorWithMessage(pos, pos, pos, reportMessage) } func (w *WSL) addErrorNeverAllow(pos token.Pos, ct CheckType) { - reportMessage := fmt.Sprintf("%s (never cuddle %s)", MessageMissingWhitespaceAbove, ct) + reportMessage := fmt.Sprintf("%s (never cuddle %s)", messageMissingWhitespaceAbove, ct) w.addErrorWithMessage(pos, pos, pos, reportMessage) } @@ -1012,20 +1014,20 @@ func (w *WSL) addError(report, start, end token.Pos, message string, ct CheckTyp } func (w *WSL) addErrorWithMessage(report, start, end token.Pos, message string) { - issue, ok := w.Issues[report] + iss, ok := w.issues[report] if !ok { - issue = Issue{ - Message: message, - FixRanges: []FixRange{}, + iss = issue{ + message: message, + fixRanges: []fixRange{}, } } - issue.FixRanges = append(issue.FixRanges, FixRange{ - FixRangeStart: start, - FixRangeEnd: end, + iss.fixRanges = append(iss.fixRanges, fixRange{ + fixRangeStart: start, + fixRangeEnd: end, }) - w.Issues[report] = issue + w.issues[report] = iss } func hasIntersection(a, b ast.Node) bool { From 2fad2661a52f7a2315d6a77b376460be4ead78c4 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 22:08:07 +0200 Subject: [PATCH 096/112] Improve `CompositeLit` --- testdata/src/default_config/expr/expr.go | 10 ++++++++++ testdata/src/default_config/expr/expr.go.golden | 9 +++++++++ wsl.go | 8 +++++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/testdata/src/default_config/expr/expr.go b/testdata/src/default_config/expr/expr.go index ca608a1..8b7af20 100644 --- a/testdata/src/default_config/expr/expr.go +++ b/testdata/src/default_config/expr/expr.go @@ -44,3 +44,13 @@ func fn4() { b := 2 <-ch // want `missing whitespace above this line \(no shared variables above expr\)` } + +func fn5() { + s := []string{ + func() string { // want +1 `unnecessary whitespace \(leading-whitespace\)` + + "a" + }, + "b", + } +} diff --git a/testdata/src/default_config/expr/expr.go.golden b/testdata/src/default_config/expr/expr.go.golden index dcedb91..3c52043 100644 --- a/testdata/src/default_config/expr/expr.go.golden +++ b/testdata/src/default_config/expr/expr.go.golden @@ -48,3 +48,12 @@ func fn4() { <-ch // want `missing whitespace above this line \(no shared variables above expr\)` } + +func fn5() { + s := []string{ + func() string { // want +1 `unnecessary whitespace \(leading-whitespace\)` + "a" + }, + "b", + } +} diff --git a/wsl.go b/wsl.go index 635c137..11a2cd7 100644 --- a/wsl.go +++ b/wsl.go @@ -136,16 +136,18 @@ func (w *WSL) checkExpr(expr ast.Expr, cursor *Cursor) { for _, e := range s.Args { w.checkExpr(e, cursor) } - case *ast.IndexExpr: - w.checkExpr(s.X, cursor) case *ast.StarExpr: w.checkExpr(s.X, cursor) + case *ast.CompositeLit: + for _, e := range s.Elts { + w.checkExpr(e, cursor) + } case *ast.ArrayType, *ast.BasicLit, *ast.BinaryExpr, *ast.ChanType, - *ast.CompositeLit, *ast.Ident, + *ast.IndexListExpr, *ast.MapType, *ast.ParenExpr, *ast.SelectorExpr, From 1d05253d3d9b6eaa5aa87d7cbe2768c6b1f59aec Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sun, 11 May 2025 22:11:33 +0200 Subject: [PATCH 097/112] Ignore more expr --- wsl.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wsl.go b/wsl.go index 11a2cd7..c9ca00a 100644 --- a/wsl.go +++ b/wsl.go @@ -146,8 +146,11 @@ func (w *WSL) checkExpr(expr ast.Expr, cursor *Cursor) { *ast.BasicLit, *ast.BinaryExpr, *ast.ChanType, + *ast.Ellipsis, *ast.Ident, + *ast.IndexExpr, *ast.IndexListExpr, + *ast.KeyValueExpr, *ast.MapType, *ast.ParenExpr, *ast.SelectorExpr, From 2291ec2530ef8021490f736633518a616edeaf3d Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 12 May 2025 20:24:04 +0200 Subject: [PATCH 098/112] Add test for else-if/else blocks --- testdata/src/default_config/if/if.go | 16 ++++++++++++++++ testdata/src/default_config/if/if.go.golden | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/testdata/src/default_config/if/if.go b/testdata/src/default_config/if/if.go index 58f3673..1d797bc 100644 --- a/testdata/src/default_config/if/if.go +++ b/testdata/src/default_config/if/if.go @@ -49,3 +49,19 @@ func fn5(m any, k string) string { return k } + +func fn6() { + if true { + _ = 1 + _ = 2 + return // want `missing whitespace above this line \(too many lines above return\)` + } else if false { + _ = 1 + _ = 2 + return // want `missing whitespace above this line \(too many lines above return\)` + } else { + _ = 1 + _ = 2 + return // want `missing whitespace above this line \(too many lines above return\)` + } +} diff --git a/testdata/src/default_config/if/if.go.golden b/testdata/src/default_config/if/if.go.golden index 8c8f794..f61ba5b 100644 --- a/testdata/src/default_config/if/if.go.golden +++ b/testdata/src/default_config/if/if.go.golden @@ -52,3 +52,22 @@ func fn5(m any, k string) string { return k } + +func fn6() { + if true { + _ = 1 + _ = 2 + + return // want `missing whitespace above this line \(too many lines above return\)` + } else if false { + _ = 1 + _ = 2 + + return // want `missing whitespace above this line \(too many lines above return\)` + } else { + _ = 1 + _ = 2 + + return // want `missing whitespace above this line \(too many lines above return\)` + } +} From 3805ef1c14e1a08668fdbb7e0f5e484e804f882e Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 12 May 2025 20:29:37 +0200 Subject: [PATCH 099/112] Add test for multiline case --- .../whitespace/leading_whitespace.go | 29 +++++++++++++++++++ .../whitespace/leading_whitespace.go.golden | 26 +++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go b/testdata/src/default_config/whitespace/leading_whitespace.go index ec60f77..7385b14 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go +++ b/testdata/src/default_config/whitespace/leading_whitespace.go @@ -126,3 +126,32 @@ func fn9() { } } + +func fn10() { + switch { + case true || false: + fmt.Println("ok") + case true, false: + fmt.Println("ok") + case true || false: + fmt.Println("ok") + case true, + false: // want +1 `unnecessary whitespace \(leading-whitespace\)` + + fmt.Println("nok") + case true || + false: // want +1 `unnecessary whitespace \(leading-whitespace\)` + + fmt.Println("nok") + case true, + false: + fmt.Println("ok") + + case true || + false: + fmt.Println("ok") + + case true, false: + fmt.Println("all") + } +} diff --git a/testdata/src/default_config/whitespace/leading_whitespace.go.golden b/testdata/src/default_config/whitespace/leading_whitespace.go.golden index cda122d..91a81d4 100644 --- a/testdata/src/default_config/whitespace/leading_whitespace.go.golden +++ b/testdata/src/default_config/whitespace/leading_whitespace.go.golden @@ -108,3 +108,29 @@ func fn9() { } } +func fn10() { + switch { + case true || false: + fmt.Println("ok") + case true, false: + fmt.Println("ok") + case true || false: + fmt.Println("ok") + case true, + false: // want +1 `unnecessary whitespace \(leading-whitespace\)` + fmt.Println("nok") + case true || + false: // want +1 `unnecessary whitespace \(leading-whitespace\)` + fmt.Println("nok") + case true, + false: + fmt.Println("ok") + + case true || + false: + fmt.Println("ok") + + case true, false: + fmt.Println("all") + } +} From 75620899ba9db842ce307e8c4ca32d62169a0fc2 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 12 May 2025 21:02:05 +0200 Subject: [PATCH 100/112] Don't check block list for switch --- testdata/src/default_config/switch/switch.go | 29 +++++++++++++++++++ .../default_config/switch/switch.go.golden | 29 +++++++++++++++++++ wsl.go | 8 ++++- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/testdata/src/default_config/switch/switch.go b/testdata/src/default_config/switch/switch.go index 86dc03f..3ae5dc3 100644 --- a/testdata/src/default_config/switch/switch.go +++ b/testdata/src/default_config/switch/switch.go @@ -1,5 +1,14 @@ package testpkg +func fn0() { + a := 1 + switch a { + case 1: + case 2: + panic(a) + } +} + func fn1() { a := 1 b := 2 // want `missing whitespace above this line \(too many statements above switch\)` @@ -33,3 +42,23 @@ func fn3() { panic("") } } + +func fn4() { + timeout := 10 + switch timeout { + case 5: + timeout = 100 + case 10: + timeout = 0 + } +} + +func fn5() { + timeout := 10 + switch { // want `missing whitespace above this line \(no shared variables above switch\)` + case timeout > 5: + timeout = 100 + case timeout == 10: + timeout = 0 + } +} diff --git a/testdata/src/default_config/switch/switch.go.golden b/testdata/src/default_config/switch/switch.go.golden index 1f1ad06..e84d6d8 100644 --- a/testdata/src/default_config/switch/switch.go.golden +++ b/testdata/src/default_config/switch/switch.go.golden @@ -1,5 +1,14 @@ package testpkg +func fn0() { + a := 1 + switch a { + case 1: + case 2: + panic(a) + } +} + func fn1() { a := 1 @@ -37,3 +46,23 @@ func fn3() { } } +func fn4() { + timeout := 10 + switch timeout { + case 5: + timeout = 100 + case 10: + timeout = 0 + } +} + +func fn5() { + timeout := 10 + + switch { // want `missing whitespace above this line \(no shared variables above switch\)` + case timeout > 5: + timeout = 100 + case timeout == 10: + timeout = 0 + } +} diff --git a/wsl.go b/wsl.go index c9ca00a..28d9c14 100644 --- a/wsl.go +++ b/wsl.go @@ -926,7 +926,13 @@ func (w *WSL) maybeCheckBlock( if _, ok := w.config.Checks[check]; ok { cursor.SetChecker(check) - w.checkCuddlingBlock(node, blockStmt.List, cursor, 1) + + blockList := []ast.Stmt{} + if check != CheckSwitch && check != CheckTypeSwitch { + blockList = blockStmt.List + } + + w.checkCuddlingBlock(node, blockList, cursor, 1) } } From e2fee7d9328fb0eb6b8e022eeb3a082fc6f8ae21 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 12 May 2025 21:58:27 +0200 Subject: [PATCH 101/112] Keep and convert `AllowAssignAndCall` to check --- README.md | 3 ++ RULES.md | 42 +++++++++++++++++++ cmd/golangci-lint-migrate/README.md | 2 +- cmd/golangci-lint-migrate/main.go | 6 ++- config.go | 12 ++++++ testdata/src/default_config/assign/assign.go | 3 +- .../default_config/assign/assign.go.golden | 3 +- .../with_config/assign_expr/assign_expr.go | 7 ++++ .../assign_expr/assign_expr.go.golden | 9 ++++ wsl.go | 26 +++++------- wsl_test.go | 6 +++ 11 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 testdata/src/with_config/assign_expr/assign_expr.go create mode 100644 testdata/src/with_config/assign_expr/assign_expr.go.golden diff --git a/README.md b/README.md index ea2b408..f76ee6f 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ For more details and examples, see [RULES](RULES.md). - ❌ **assign-exclusive** - Only allow cuddling either new variables or reassigning of existing ones +- ❌ **assign-expr** - Don't allow assignments to be cuddled with expressions, + e.g. function calls - ✅ **append** - Only allow reassigning with `append` if the value being appended exist on the line above - ❌ **err** - Error checking must follow immediately after the error variable is @@ -157,6 +159,7 @@ linters: - trailing-whitespace disable: - assign-exclusive + - assign-expr - err ``` diff --git a/RULES.md b/RULES.md index c6e5716..f8903e6 100644 --- a/RULES.md +++ b/RULES.md @@ -786,6 +786,48 @@ Separating assignments and re-assignments. +## `assign-expr` + +Assignments are allowed to be cuddled with expressions, primarily to support +mixing assignments and function calls which can often make sense in shorter +flows. By enabling this check `wsl` will ensure assignments are not cuddled with +expressions. + + + + + + + +
BadGood
+ +```go +t1.Fn1() +x := t1.Fn2() +t1.Fn3() +``` + + + +```go +t1.Fn1() + +x := t1.Fn2() +t1.Fn3() +``` + +
+ +Assignment is followed directly after an expression. Even though they share +variables this is not allowed when using `assign-expr`. + + + +Assignment is separated from the expression above since it's not allowed to +cuddle the assignment with an expression. + +
+ ## `append` Append enables strict `append` checking where assignments that are diff --git a/cmd/golangci-lint-migrate/README.md b/cmd/golangci-lint-migrate/README.md index 1c340ad..1079150 100644 --- a/cmd/golangci-lint-migrate/README.md +++ b/cmd/golangci-lint-migrate/README.md @@ -42,7 +42,7 @@ linters: See [repo] and [rules] for details but in short, this is the change: - **strict-append** - Converted to a check called `append` -- **allow-assign-and-call** - Deprecated +- **allow-assign-and-call** - Converted to check called `assign-expr` - **allow-assign-and-anything** - Converted to a check called `assign` - **allow-multiline-assign** - Deprecated, always allowed - **force-case-trailing-whitespace** - Renamed to `case-max-lines` diff --git a/cmd/golangci-lint-migrate/main.go b/cmd/golangci-lint-migrate/main.go index e966ac0..13d68b2 100644 --- a/cmd/golangci-lint-migrate/main.go +++ b/cmd/golangci-lint-migrate/main.go @@ -40,7 +40,7 @@ type NewConfig struct { } // StrictAppend is replaced with CheckAppend -// AllowAssignAndCall is in TODO for deprecation - needs investigation +// AllowAssignAndCall is replaced with CheckAssignExpr (inverse) // AllowAssignAndAnything is replaced with CheckAssign // AllowMultilineAssign is deprecated and always allowed in v5 // AllowTrailingComment is deprecated and always allowed in v5 @@ -133,6 +133,10 @@ func main() { v5cfg.Enable = append(v5cfg.Enable, wsl.CheckAssignExclusive.String()) } + if !v1cfg.AllowAssignAndCall { + v5cfg.Enable = append(v5cfg.Enable, wsl.CheckAssignExpr.String()) + } + slices.Sort(v5cfg.Enable) slices.Sort(v5cfg.Disable) diff --git a/config.go b/config.go index fd1ac7a..bc9dcab 100644 --- a/config.go +++ b/config.go @@ -43,6 +43,14 @@ const ( // b = 2 // . CheckAssignExclusive + // CheckAssignExpr will check so assignments are not cuddled with expression + // nodes, e.g. + // + // t1.Fn1() + // + // x := t1.Fn2() + // t1.Fn3() + CheckAssignExpr // Check append only allows assignments of `append` to be cuddled with other // assignments if it's a variable used in the append statement, e.g. // @@ -87,6 +95,7 @@ func (c CheckType) String() string { "type-switch", // "assign-exclusive", + "assign-expr", "append", "err", "leading-whitespace", @@ -198,6 +207,7 @@ func DefaultChecks() CheckSet { func AllChecks() CheckSet { c := DefaultChecks() c.Add(CheckAssignExclusive) + c.Add(CheckAssignExpr) c.Add(CheckErr) return c @@ -254,6 +264,8 @@ func CheckFromString(s string) (CheckType, error) { return CheckAppend, nil case "assign-exclusive": return CheckAssignExclusive, nil + case "assign-expr": + return CheckAssignExpr, nil case "err": return CheckErr, nil case "leading-whitespace": diff --git a/testdata/src/default_config/assign/assign.go b/testdata/src/default_config/assign/assign.go index 2b560ef..e3edd6d 100644 --- a/testdata/src/default_config/assign/assign.go +++ b/testdata/src/default_config/assign/assign.go @@ -47,7 +47,8 @@ func assignAndCall() { t2 := NewT() t1.Fn() - t2.I = t1.Fn() + x := t1.Fn() + t1.Fn() } func closureInCall() { diff --git a/testdata/src/default_config/assign/assign.go.golden b/testdata/src/default_config/assign/assign.go.golden index 8403a2e..1dad1b5 100644 --- a/testdata/src/default_config/assign/assign.go.golden +++ b/testdata/src/default_config/assign/assign.go.golden @@ -48,7 +48,8 @@ func assignAndCall() { t2 := NewT() t1.Fn() - t2.I = t1.Fn() + x := t1.Fn() + t1.Fn() } func closureInCall() { diff --git a/testdata/src/with_config/assign_expr/assign_expr.go b/testdata/src/with_config/assign_expr/assign_expr.go new file mode 100644 index 0000000..92cd61b --- /dev/null +++ b/testdata/src/with_config/assign_expr/assign_expr.go @@ -0,0 +1,7 @@ +package testpkg + +func assignAndCall(t1 SomeT) { + t1.Fn() + x := t1.Fn() // want `missing whitespace above this line \(invalid statement above assign\)` + t1.Fn() +} diff --git a/testdata/src/with_config/assign_expr/assign_expr.go.golden b/testdata/src/with_config/assign_expr/assign_expr.go.golden new file mode 100644 index 0000000..84a7146 --- /dev/null +++ b/testdata/src/with_config/assign_expr/assign_expr.go.golden @@ -0,0 +1,9 @@ +package testpkg + +func assignAndCall(t1 SomeT) { + t1.Fn() + + x := t1.Fn() // want `missing whitespace above this line \(invalid statement above assign\)` + t1.Fn() +} + diff --git a/wsl.go b/wsl.go index 28d9c14..aa40c3b 100644 --- a/wsl.go +++ b/wsl.go @@ -302,6 +302,10 @@ func (w *WSL) checkCuddlingMaxAllowed( } func (w *WSL) checkCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { + if w.numberOfStatementsAbove(cursor) == 0 { + return + } + previousNode := cursor.PreviousNode() currAssign, currIsAssign := stmt.(*ast.AssignStmt) @@ -336,27 +340,17 @@ func (w *WSL) checkCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { prevIsValidType := previousNode == nil || prevIsAssign || prevIsDecl || prevIsIncDec - // TODO: This is `allow-assign-and-call`/`AllowAssignAndCallCuddle`, should - // we deprecate it? - // ref: https://github.com/bombsimon/wsl/blob/52299dcd5c1c2a8baf77b4be4508937486d43656/wsl.go#L559-L563 - // 1. It's not actually checking call - just that we have intersections - // 2. It's a bit too niche I think, either we support assign and call (or - // whatever) or we don't. - // 3. With the new check config, one could just disable checks for assign - // and it would allow cuddling with anything. - // - // This is also a bit odd because the reason it works in the current impl. - // is because we only allow this if the line above is a single line - // statement which it never is for e.g. if. - if !prevIsValidType && currIsAssign { + if _, ok := w.config.Checks[CheckAssignExpr]; !ok { if _, ok := previousNode.(*ast.ExprStmt); ok && hasIntersection(stmt, previousNode) { - return + prevIsValidType = prevIsValidType || ok } } - if w.numberOfStatementsAbove(cursor) > 0 && !prevIsValidType { - w.addErrorInvalidTypeCuddle(stmt.Pos(), cursor.checkType) + if prevIsValidType { + return } + + w.addErrorInvalidTypeCuddle(stmt.Pos(), cursor.checkType) } func (w *WSL) checkBlock(block *ast.BlockStmt) *Cursor { diff --git a/wsl_test.go b/wsl_test.go index 9b4ae15..b0cd6a7 100644 --- a/wsl_test.go +++ b/wsl_test.go @@ -73,6 +73,12 @@ func TestWithConfig(t *testing.T) { config.Checks.Add(CheckAssignExclusive) }, }, + { + subdir: "assign_expr", + configFn: func(config *Configuration) { + config.Checks.Add(CheckAssignExpr) + }, + }, { subdir: "disable_all", configFn: func(config *Configuration) { From cb836f0e1b98760a48764605cb3f19aec7ba4ede Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 12 May 2025 22:06:11 +0200 Subject: [PATCH 102/112] Docs sorting --- README.md | 4 +-- RULES.md | 96 +++++++++++++++++++++++++++---------------------------- config.go | 18 +++++------ 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index f76ee6f..a44e29f 100644 --- a/README.md +++ b/README.md @@ -62,12 +62,12 @@ For more details and examples, see [RULES](RULES.md). #### Specific `wsl` cases +- ✅ **append** - Only allow reassigning with `append` if the value being appended + exist on the line above - ❌ **assign-exclusive** - Only allow cuddling either new variables or reassigning of existing ones - ❌ **assign-expr** - Don't allow assignments to be cuddled with expressions, e.g. function calls -- ✅ **append** - Only allow reassigning with `append` if the value being appended - exist on the line above - ❌ **err** - Error checking must follow immediately after the error variable is assigned - ✅ **leading-whitespace** - Disallow leading empty lines in blocks diff --git a/RULES.md b/RULES.md index f8903e6..8f41449 100644 --- a/RULES.md +++ b/RULES.md @@ -746,10 +746,12 @@ See [`for`](#for), same rules apply but for the keyword `range`. > Configurable via `allow-whole-block` to allow cuddling if the variable is used > _anywhere_ in the following block (disabled by default). -## `assign-exclusive` +## `append` -Assign exclusive does not allow mixing new assignments (`:=`) with -re-assignments (`=`). +Append enables strict `append` checking where assignments that are +re-assignments with `append` (e.g. `x = append(x, y)`) is only allowed to be +cuddled with other assignments if the `append` uses the variable on the line +above. @@ -757,41 +759,46 @@ re-assignments (`=`).
BadGood
```go +s := []string{} + a := 1 -b = 2 -c := 3 -d = 4 +s = append(s, 2) +b := 3 +s = append(s, a) ``` ```go +s := []string{} + a := 1 -c := 3 +s = append(s, a) -b = 2 -d = 4 +b := 3 + +s = append(s, 2) ```
-Alternating new assignments and re-assignments. +Assignments not related to the slice appending is mixed and matched, making +context unclear. -Separating assignments and re-assignments. +Assignments used in the appending are cuddled since they share context, other +assignments are separated with a newline.
-## `assign-expr` +## `assign-exclusive` -Assignments are allowed to be cuddled with expressions, primarily to support -mixing assignments and function calls which can often make sense in shorter -flows. By enabling this check `wsl` will ensure assignments are not cuddled with -expressions. +Assign exclusive does not allow mixing new assignments (`:=`) with +re-assignments (`=`). @@ -799,41 +806,41 @@ expressions.
BadGood
```go -t1.Fn1() -x := t1.Fn2() -t1.Fn3() +a := 1 +b = 2 +c := 3 +d = 4 ``` ```go -t1.Fn1() +a := 1 +c := 3 -x := t1.Fn2() -t1.Fn3() +b = 2 +d = 4 ```
-Assignment is followed directly after an expression. Even though they share -variables this is not allowed when using `assign-expr`. +Alternating new assignments and re-assignments. -Assignment is separated from the expression above since it's not allowed to -cuddle the assignment with an expression. +Separating assignments and re-assignments.
-## `append` +## `assign-expr` -Append enables strict `append` checking where assignments that are -re-assignments with `append` (e.g. `x = append(x, y)`) is only allowed to be -cuddled with other assignments if the `append` uses the variable on the line -above. +Assignments are allowed to be cuddled with expressions, primarily to support +mixing assignments and function calls which can often make sense in shorter +flows. By enabling this check `wsl` will ensure assignments are not cuddled with +expressions. @@ -841,38 +848,31 @@ above.
BadGood
```go -s := []string{} - -a := 1 -s = append(s, 2) -b := 3 -s = append(s, a) +t1.Fn1() +x := t1.Fn2() +t1.Fn3() ``` ```go -s := []string{} - -a := 1 -s = append(s, a) - -b := 3 +t1.Fn1() -s = append(s, 2) +x := t1.Fn2() +t1.Fn3() ```
-Assignments not related to the slice appending is mixed and matched, making -context unclear. +Assignment is followed directly after an expression. Even though they share +variables this is not allowed when using `assign-expr`. -Assignments used in the appending are cuddled since they share context, other -assignments are separated with a newline. +Assignment is separated from the expression above since it's not allowed to +cuddle the assignment with an expression.
diff --git a/config.go b/config.go index bc9dcab..8da233a 100644 --- a/config.go +++ b/config.go @@ -33,6 +33,13 @@ const ( CheckSwitch CheckTypeSwitch + // Check append only allows assignments of `append` to be cuddled with other + // assignments if it's a variable used in the append statement, e.g. + // + // a := 1 + // x = append(x, a) + // . + CheckAppend // Assign exclusive only allows assignments of either new variables or // re-assignment of existing ones, e.g. // @@ -51,13 +58,6 @@ const ( // x := t1.Fn2() // t1.Fn3() CheckAssignExpr - // Check append only allows assignments of `append` to be cuddled with other - // assignments if it's a variable used in the append statement, e.g. - // - // a := 1 - // x = append(x, a) - // . - CheckAppend // Force error checking to follow immediately after an error variable is // assigned, e.g. // @@ -94,9 +94,9 @@ func (c CheckType) String() string { "switch", "type-switch", // + "append", "assign-exclusive", "assign-expr", - "append", "err", "leading-whitespace", "trailing-whitespace", @@ -182,8 +182,8 @@ func NewCheckSet( func DefaultChecks() CheckSet { return CheckSet{ - CheckAssign: {}, CheckAppend: {}, + CheckAssign: {}, CheckBranch: {}, CheckDecl: {}, CheckDefer: {}, From b56fb05deb1fa8c8d49a55856b94d6c23cb1608c Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 12 May 2025 22:47:49 +0200 Subject: [PATCH 103/112] Fix lint issues --- .golangci.yml | 1 + config.go | 1 + wsl.go | 12 ++++++------ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 0cb46b1..8386303 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -21,6 +21,7 @@ linters: - funlen - gocognit - gocyclo + - godot - godox - lll - maintidx diff --git a/config.go b/config.go index 8da233a..df5e650 100644 --- a/config.go +++ b/config.go @@ -57,6 +57,7 @@ const ( // // x := t1.Fn2() // t1.Fn3() + // . CheckAssignExpr // Force error checking to follow immediately after an error variable is // assigned, e.g. diff --git a/wsl.go b/wsl.go index aa40c3b..d4f2afb 100644 --- a/wsl.go +++ b/wsl.go @@ -33,7 +33,6 @@ type WSL struct { file *ast.File fset *token.FileSet typeInfo *types.Info - comments ast.CommentMap issues map[token.Pos]issue config *Configuration } @@ -124,6 +123,7 @@ func (w *WSL) checkStmt(stmt ast.Stmt, cursor *Cursor) { } } +//nolint:unparam // False positive on `cursor` func (w *WSL) checkExpr(expr ast.Expr, cursor *Cursor) { switch s := expr.(type) { // func() {} @@ -189,14 +189,12 @@ func (w *WSL) checkSpec(spec ast.Spec, cursor *Cursor) { } } -func (w *WSL) checkBody(body []ast.Stmt) *Cursor { +func (w *WSL) checkBody(body []ast.Stmt) { cursor := NewCursor(body) for cursor.Next() { w.checkStmt(cursor.Stmt(), cursor) } - - return cursor } func (w *WSL) checkCuddlingBlock(stmt ast.Node, blockList []ast.Stmt, cursor *Cursor, maxAllowedStatements int) { @@ -353,11 +351,11 @@ func (w *WSL) checkCuddlingWithoutIntersection(stmt ast.Node, cursor *Cursor) { w.addErrorInvalidTypeCuddle(stmt.Pos(), cursor.checkType) } -func (w *WSL) checkBlock(block *ast.BlockStmt) *Cursor { +func (w *WSL) checkBlock(block *ast.BlockStmt) { w.checkBlockLeadingNewline(block) w.checkTrailingNewline(block) - return w.checkBody(block.List) + w.checkBody(block.List) } func (w *WSL) checkCaseClause(stmt *ast.CaseClause, cursor *Cursor) { @@ -735,6 +733,7 @@ func (w *WSL) checkSend(stmt *ast.SendStmt, cursor *Cursor) { cursor.SetChecker(CheckSend) var stmts []ast.Stmt + ast.Inspect(stmt.Value, func(n ast.Node) bool { if b, ok := n.(*ast.BlockStmt); ok { stmts = b.List @@ -767,6 +766,7 @@ func (w *WSL) checkCaseTrailingNewline(body []ast.Stmt, cursor *Cursor) { } var nextCase ast.Node + switch n := cursor.Stmt().(type) { case *ast.CaseClause: nextCase = n From ff92a29c18dc8028b4eea28b998b4d0226657f94 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 12 May 2025 23:13:05 +0200 Subject: [PATCH 104/112] Remove print statements used for debugging --- wsl.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wsl.go b/wsl.go index d4f2afb..cd60f14 100644 --- a/wsl.go +++ b/wsl.go @@ -119,7 +119,6 @@ func (w *WSL) checkStmt(stmt ast.Stmt, cursor *Cursor) { w.checkLabel(s, cursor) case *ast.EmptyStmt: default: - fmt.Printf("Not implemented stmt: %T\n", s) } } @@ -159,7 +158,6 @@ func (w *WSL) checkExpr(expr ast.Expr, cursor *Cursor) { *ast.UnaryExpr, nil: default: - fmt.Printf("Not implemented expr: %T\n", s) } } @@ -173,7 +171,6 @@ func (w *WSL) checkDecl(decl ast.Decl, cursor *Cursor) { w.checkStmt(d.Body, cursor) case *ast.BadDecl: default: - fmt.Printf("Not implemented decl: %T\n", d) } } @@ -185,7 +182,6 @@ func (w *WSL) checkSpec(spec ast.Spec, cursor *Cursor) { } case *ast.ImportSpec, *ast.TypeSpec: default: - fmt.Printf("Not implemented spec: %T\n", s) } } From e35dcd2068b7708c0d3a8125ebbaee1b92adf832 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 12 May 2025 23:17:13 +0200 Subject: [PATCH 105/112] Change badge branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a44e29f..47ffc27 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # wsl - Whitespace Linter [![GitHub Actions](https://github.com/bombsimon/wsl/actions/workflows/go.yml/badge.svg)](https://github.com/bombsimon/wsl/actions/workflows/go.yml) -[![Coverage Status](https://coveralls.io/repos/github/bombsimon/wsl/badge.svg?branch=master)](https://coveralls.io/github/bombsimon/wsl?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/bombsimon/wsl/badge.svg?branch=main)](https://coveralls.io/github/bombsimon/wsl?branch=main) `wsl` (**w**hite**s**pace **l**inter) is a linter that wants you to use empty lines to separate grouping of different types to increase readability. There are From 9c794990516a339f879f572771a9943e45846518 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Sat, 17 May 2025 10:10:29 +0200 Subject: [PATCH 106/112] Remove no longer valid deprecation log --- cmd/golangci-lint-migrate/main.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cmd/golangci-lint-migrate/main.go b/cmd/golangci-lint-migrate/main.go index 13d68b2..81cf1f9 100644 --- a/cmd/golangci-lint-migrate/main.go +++ b/cmd/golangci-lint-migrate/main.go @@ -101,10 +101,6 @@ func main() { v5cfg.Disable = append(v5cfg.Disable, wsl.CheckAppend.String()) } - if v1cfg.AllowAssignAndCall { - log.Println("`allow-assign-and-call` is deprecated in >= v5, you can disable assign checks completely") - } - if v1cfg.AllowAssignAndAnything { v5cfg.Disable = append(v5cfg.Disable, wsl.CheckAssign.String()) } From 41e6fca6578b59621f56a3324595c7262ec455ab Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 2 Jun 2025 21:18:11 +0200 Subject: [PATCH 107/112] Support Go 1.23 --- go.mod | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 69ce1ec..d25148b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/bombsimon/wsl/v5 -go 1.24 +go 1.23.0 + +toolchain go1.24.1 require ( github.com/stretchr/testify v1.9.0 From 5cdc3d8b42456a9e70e936424b8f57bcd870a038 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 2 Jun 2025 21:25:13 +0200 Subject: [PATCH 108/112] Don't use `SplitSq` when supporting Go 1.23 --- analyzer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzer.go b/analyzer.go index 248db23..db2b852 100644 --- a/analyzer.go +++ b/analyzer.go @@ -151,7 +151,7 @@ type multiStringValue struct { func (m *multiStringValue) Set(value string) error { var s []string - for v := range strings.SplitSeq(value, ",") { + for _, v := range strings.Split(value, ",") { s = append(s, strings.TrimSpace(v)) } From ba6ca98ee6dd2a4e13fbca26f318ea4292df5b64 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Mon, 2 Jun 2025 21:49:10 +0200 Subject: [PATCH 109/112] Rename `RULES.md` to `CHECKS.md` --- RULES.md => CHECKS.md | 0 README.md | 2 +- cmd/golangci-lint-migrate/README.md | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename RULES.md => CHECKS.md (100%) diff --git a/RULES.md b/CHECKS.md similarity index 100% rename from RULES.md rename to CHECKS.md diff --git a/README.md b/README.md index 47ffc27..ccf5dd4 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The base rule is that statements that has a block (e.g. `for`, `range`, `switch`, `if` etc) should always only be directly adjacent with a single variable and only if it's used in the expression in the block itself. -For more details and examples, see [RULES](RULES.md). +For more details and examples, see [CHECKS](CHECKS.md). ✅ = enabled by default, ❌ = disabled by default diff --git a/cmd/golangci-lint-migrate/README.md b/cmd/golangci-lint-migrate/README.md index 1079150..987cd9e 100644 --- a/cmd/golangci-lint-migrate/README.md +++ b/cmd/golangci-lint-migrate/README.md @@ -39,7 +39,7 @@ linters: ## Info -See [repo] and [rules] for details but in short, this is the change: +See [repo] and [checks] for details but in short, this is the change: - **strict-append** - Converted to a check called `append` - **allow-assign-and-call** - Converted to check called `assign-expr` @@ -56,4 +56,4 @@ See [repo] and [rules] for details but in short, this is the change: - **force-short-decl-cuddling** - Converted to a check called `assign-exclusive` [repo]: https://github.com/bombsimon/wsl/blob/main/README.md -[rules]: https://github.com/bombsimon/wsl/blob/main/RULES.md +[checks]: https://github.com/bombsimon/wsl/blob/main/CHECKS.md From 5e658033a8e2a900d856e03998ecf1544a149dcf Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 4 Jun 2025 22:24:45 +0200 Subject: [PATCH 110/112] Improve documentation --- CHECKS.md | 564 +++++++++++++++++++++++++++++++++++++++++------------- README.md | 5 +- 2 files changed, 435 insertions(+), 134 deletions(-) diff --git a/CHECKS.md b/CHECKS.md index 8f41449..6a34086 100644 --- a/CHECKS.md +++ b/CHECKS.md @@ -17,12 +17,12 @@ with other assignments, declarations or increment/decrement. if true { fmt.Println("hello") } -a := 1 +a := 1 // 1 defer func() { fmt.Println("hello") }() -a := 1 +a := 1 // 2 ``` @@ -49,13 +49,11 @@ c := 3 -Assignments cuddled with statements that are not assignments, e.g. `if` or -`defer`. +1 Not an assign statement above - +2 Not an assign statement above -The assignment is separated from non-assignment statement with an empty line. -This also shows multiple assignments cuddled together which is allowed. + @@ -81,7 +79,7 @@ for { } fmt.Println(a) - break + break // 1 } ``` @@ -109,14 +107,10 @@ for { -The block with the `break` control flow spans over quite some lines so it's easy -to miss the cuddled control flow. +1 Block is more than 2 lines so should be a blank line above -In the bigger block the control flow is separated with an empty line. For -smaller blocks with only 2 lines it's ok to cuddled the `break`. - @@ -137,16 +131,16 @@ assignment increasing readability. ```go var a string -var b int +var b int // 1 const a = 1 -const b = 2 +const b = 2 // 2 a := 1 -var b string +var b string // 3 fmt.Println("hello") -var a string +var a string // 4 ``` @@ -175,13 +169,15 @@ var a string -Declarations mixed with both assignments and expressions. Consecutive -declarations are not grouped together. +1 Multiple declarations should be grouped to one - +2 Multiple declarations should be grouped to one -All declarations are grouped in a single declaration, aligning them for -increased readability. Declarations are separated from other statements. +3 Declaration should always have a whitespace above + +4 Declaration should always have a whitespace above + + @@ -200,11 +196,11 @@ deferred and there should only be one statement above. val, closeFn := SomeFn() val2 := fmt.Sprintf("v-%s", val) fmt.Println(val) -defer closeFn() +defer closeFn() // 1 defer fn1() a := 1 -defer fn3() +defer fn3() // 2 f, err := os.Open("/path/to/f.txt") if err != nil { @@ -213,10 +209,7 @@ if err != nil { lines := ReadFile(f) trimLines(lines) -defer f.Close() - -m.Lock() -defer m.Unlock() +defer f.Close() // 3 ``` @@ -243,15 +236,13 @@ defer m.Unlock() -Defer calls not related to the what's being deferred. Squeezing extra statements -between assignments and when the defer happens makes it easier to lose context -of what's actually being deferred. +1 More than a single statement between `defer` and `closeFn` - +2 `a` is not used in expression + +3 More than a single statement between `defer` and `f.Close` -Examples of defer statements close to its context. Immediately after a function -variable is assigned, multiple defer in a row, immediately after error handling -and immediately after a mutex is locked. + @@ -269,7 +260,7 @@ Expressions can be multiple things and a big part of them are not handled by ```go a := 1 b := 2 -fmt.Println("not b") +fmt.Println("not b") // 1 ``` @@ -288,25 +279,22 @@ fmt.Println(a) -The function call to `Println` isn't related to the variables on the line above. +1 `b` is not used in expression -The call to `Println` uses the variable immediately above and is therefor in the -same context as the assignment. - ## `for` -See [`if`](#if), same rules apply but for the keyword `for`. - > Configurable via `allow-first-in-block` to allow cuddling if the variable is > used _first_ in the block (enabled by default). > > Configurable via `allow-whole-block` to allow cuddling if the variable is used > _anywhere_ in the following block (disabled by default). +> +> See [Configuration](#configuration) for details. @@ -315,12 +303,18 @@ See [`if`](#if), same rules apply but for the keyword `for`. ```go i := 0 -for j := 0; j < 3; j++ { +for j := 0; j < 3; j++ { // 1 + fmt.Println(j) +} + +a := 0 +i := 3 +for j := 0; j < i; j++ { // 2 fmt.Println(j) } x := 1 -for { +for { // 3 fmt.Println("hello") break } @@ -334,6 +328,13 @@ for j := 0; j < i; j++ { fmt.Println(j) } +a := 0 + +i := 3 +for j := 0; j < i; j++ { + fmt.Println(j) +} + // Allowed with `allow-first-in-block` x := 1 for { @@ -356,19 +357,66 @@ for {
BadGood
-Variables above the `for` loop is not used in the `for` statement. +1 `i` is not used in expression - +2 More than one variable above statement -The variable on the line above is used in the loop condition. Additionally there -are examples for `allow-first-in-block` and `allow-whole-block`. +3 No variable in expression + +
## `go` -See [`defer`](#defer), same rules apply but for the keyword `go`. + + + + + + +
BadGood
+ +```go +someFunc := func() {} +go anotherFunc() // 1 + +x := 1 +go func () // 2 + fmt.Println(y) +}() + +someArg := 1 +go Fn(notArg) // 3 +``` + + + +```go +someFunc := func() {} +go someFunc() + +x := 1 +go func (s string) { + fmt.Println(s) +}(x) + +someArg := 1 +go Fn(someArg) +``` + +
+ +1 `someFunc` is not used in expression + +2 `x` is not used in expression + +3 `someArg` is not used in expression + + + +
## `if` @@ -377,6 +425,8 @@ See [`defer`](#defer), same rules apply but for the keyword `go`. > > Configurable via `allow-whole-block` to allow cuddling if the variable is used > _anywhere_ in the following block (disabled by default). +> +> See [Configuration](#configuration) for details. `if` statements are one of several block statements (a statement with a block) that can have some form of expression or condition. To make block context more @@ -390,30 +440,30 @@ the variable must be used in the condition (unless configured otherwise). ```go x := 1 -if y > 1 { +if y > 1 { // 1 fmt.Println("y > 1") } a := 1 b := 2 -if b > 1 { +if b > 1 { // 2 fmt.Println("a > 1") } a := 1 b := 2 -if a > 1 { +if a > 1 { // 3 fmt.Println("a > 1") } a := 1 b := 2 -if notEvenAOrB() { +if notEvenAOrB() { // 4 fmt.Println("not a or b") } a := 1 -x, err := SomeFn() +x, err := SomeFn() // 5 if err != nil { return err } @@ -477,21 +527,65 @@ if xUsedLaterInBlock() { -Multiple `if` statements where the variable on the line above is either not used -in the `if` statement or there are more than one cuddled assignment above. +1 `x` is not used in expression - +2 More than one variable above statement + +3 `b` is not used in expression and too many statements -Only when a single variable that is used in the `if` statement below is declared -or assigned do we cuddled it. This also shows examples for the configuration. +4 No variable in expression + +5 More than one variable above statement + + ## `inc-dec` -See [`assign`](#assign), same rules apply but for increment (`++`) and decrement -(`--`) + + + + + + +
BadGood
+ +```go +i := 1 + +if true { + fmt.Println("hello") +} +i++ // 1 + +defer func() { + fmt.Println("hello") +}() +i++ // 2 +``` + + + +```go +i := 1 +i++ + +i-- +j := i +j++ +``` + +
+ +1 Not an assign or inc/dec statement above + +2 Not an assign or inc/dec statement above + + + +
## `label` @@ -508,7 +602,7 @@ L1: if true { _ = 1 } -L2: +L2: // 1 if true { _ = 1 } @@ -532,24 +626,22 @@ L2: -The label `L2` is directly cuddled with the statement above. +1 Labels should always have a whitespabe above -The label `L2` has an empty line above. - ## `range` -See [`for`](#for), same rules apply but for the keyword `range`. - > Configurable via `allow-first-in-block` to allow cuddling if the variable is > used _first_ in the block (enabled by default). > > Configurable via `allow-whole-block` to allow cuddling if the variable is used > _anywhere_ in the following block (disabled by default). +> +> See [Configuration](#configuration) for details. @@ -558,19 +650,19 @@ See [`for`](#for), same rules apply but for the keyword `range`. ```go someRange := []int{1, 2, 3} -for _, i := range thisIsNotSomeRange { +for _, i := range thisIsNotSomeRange { // 1 fmt.Println(i) } x := 1 -for i := range make([]int, 3) { +for i := range make([]int, 3) { // 2 fmt.Println("hello") break } s1 := []int{1, 2, 3} s2 := []int{3, 2, 1} -for _, v := range s2 { +for _, v := range s2 { // 3 fmt.Println(v) } ``` @@ -606,13 +698,13 @@ for _, v := range s2 {
BadGood
-Slices that are not related to the `range` is cuddled with the `range` -statement. Multiple statements are cuddled above the range statement. +1 `someRange` is not used in expression - +2 `x` is not used in expression -Only variables used in the `range` are cuddled and at most one statement above the -`range` statement. +3 More than one variable above statement + +
@@ -639,7 +731,7 @@ func Fn() int { } fmt.Println(x) - return + return // 1 } ``` @@ -662,26 +754,70 @@ func Fn() int { -The block is big enough to warrant an explicit return which is otherwise easy to -miss. +1 Block is more than 2 lines so should be a blank line above -The return statement is isolated on it's own line, making it stand out a bit -more. - ## `select` -See [`for`](#for), same rules apply but for the keyword `range`. - > Configurable via `allow-first-in-block` to allow cuddling if the variable is > used _first_ in the block (enabled by default). > > Configurable via `allow-whole-block` to allow cuddling if the variable is used > _anywhere_ in the following block (disabled by default). +> +> See [Configuration](#configuration) for details. + + + + + + + +
BadGood
+ +```go +x := 0 +select { // 1 +case <-time.After(time.Second): + // ... +case <-stop: + // ... +} +``` + + + +```go +x := 0 + +select { +case <-time.After(time.Second): + // ... +case <-stop: + // ... +} + +// Allowed with `allow-whole-block` +x := 1 +select { +case <-time.After(time.Second): + // ... +case <-stop: + Fn(x) +} +``` + +
+ +1 `x` is not used in expression + + + +
## `send` @@ -695,10 +831,10 @@ the line above. ```go a := 1 -ch <- 1 +ch <- 1 // 1 b := 2 -<-ch +<-ch // 2 ``` @@ -716,35 +852,174 @@ b := 1 -Send statement cuddled with assignments that doesn't exist on the line above or -with multiple assignments. +1 `a` is not used in expression - +2 `b` is not used in expression -Send statements are only cuddled with single variables on the line above. + ## `switch` -See [`for`](#for), same rules apply but for the keyword `range`. - > Configurable via `allow-first-in-block` to allow cuddling if the variable is > used _first_ in the block (enabled by default). > > Configurable via `allow-whole-block` to allow cuddling if the variable is used > _anywhere_ in the following block (disabled by default). +> +> See [Configuration](#configuration) for details. -## `type-switch` + + + + + + +
BadGood
+ +```go +x := 0 +switch y { // 1 +case 1: + // ... +case 2: + // ... +} + + +x := 0 +y := 1 +switch y { // 2 +case 1: + // ... +case 2: + // ... +} +``` + + + +```go +x := 0 + +switch y { +case 1: + // ... +case 2: + // ... +} -See [`for`](#for), same rules apply but for the keyword `range`. + +x := 0 + +y := 1 +switch y { +case 1: + // ... +case 2: + // ... +} + +// Allowed with `allow-whole-block` +x := 1 +switch y { +case 1: + // ... +case 2: + fmt.Println(x) +} +``` + +
+ +1 `x` is not used in expression + +2 More than one variable above statement + + + +
+ +## `type-switch` > Configurable via `allow-first-in-block` to allow cuddling if the variable is > used _first_ in the block (enabled by default). > > Configurable via `allow-whole-block` to allow cuddling if the variable is used > _anywhere_ in the following block (disabled by default). +> +> See [Configuration](#configuration) for details. + + + + + + + +
BadGood
+ +```go +x := someType() +switch y.(type) { // 1 +case int32: + // ... +case int64: + // ... +} + + +x := 0 +y := someType() +switch y.(type) { +case int32: + // ... +case int64: + // ... +} +``` + + + +```go +x := someType() + +switch y.(type) { +case int32: + // ... +case int64: + // ... +} + + +x := 0 + +y := someType() +switch y.(type) { +case int32: + // ... +case int64: + // ... +} + +// Allowed with `allow-whole-block` +x := 1 +switch y.(type) { +case int32: + // ... +case int64: + fmt.Println(x) +} +``` + +
+ +1 `x` is not used in expression + + + +
## `append` @@ -762,9 +1037,9 @@ above. s := []string{} a := 1 -s = append(s, 2) +s = append(s, 2) // 1 b := 3 -s = append(s, a) +s = append(s, a) // 2 ``` @@ -784,13 +1059,11 @@ s = append(s, 2) -Assignments not related to the slice appending is mixed and matched, making -context unclear. +1 `a` is not used in append - +2 `a` is not used in append -Assignments used in the appending are cuddled since they share context, other -assignments are separated with a newline. + @@ -807,9 +1080,9 @@ re-assignments (`=`). ```go a := 1 -b = 2 -c := 3 -d = 4 +b = 2 // 1 +c := 3 // 2 +d = 4 // 3 ``` @@ -826,11 +1099,13 @@ d = 4 -Alternating new assignments and re-assignments. +1 `a` is not a re-assignment - +2 `b` is not a new assignment + +3 `c` is not a re-assignment -Separating assignments and re-assignments. + @@ -849,7 +1124,7 @@ expressions. ```go t1.Fn1() -x := t1.Fn2() +x := t1.Fn2() // 1 t1.Fn3() ``` @@ -866,14 +1141,10 @@ t1.Fn3() -Assignment is followed directly after an expression. Even though they share -variables this is not allowed when using `assign-expr`. +1 Line above is not an assignment -Assignment is separated from the expression above since it's not allowed to -cuddle the assignment with an expression. - @@ -887,7 +1158,7 @@ cuddle the assignment with an expression. ```go _, err := SomeFn() -if err != nil { +if err != nil { // 1 return fmt.Errorf("failed to fn: %w", err) } ``` @@ -905,13 +1176,10 @@ if err != nil { -The error checking is separated from the context where it was assigned with an -unnecessary newline. +1 Whitespace between error assignment and error checking -The error checking happens immediately after the assignment. - @@ -939,15 +1207,6 @@ if true { - - -The block starts with an unnecessary trailing whitespace. - - - -The block does not have any unnecessary whitespaces. - - ## `trailing-whitespace` @@ -974,13 +1233,54 @@ if true { - + -The block ends with an unnecessary trailing whitespace. +## Configuration - +One shared logic across different checks is the logic around statements +containing a block, i.e. a statement with a following `{}` (e.g. `if`, `for`, +`switch` etc). -The block does not have any unnecessary whitespaces. +`wsl` only allows one statement immediately above and that statement must also +be referenced in the expression in the statement with the block. E.g. - - +```go +someVariable := true +if someVariable { + // Here `someVariable` used in the `if` expression is the only variable + // immediately above the statement. +} +``` + +This can be configured to be more "laxed" by also allowing a single statement +immediately above if it's used either first in the following block or anywhere +inside the following block. + +### `allow-first-in-block` + +By setting this to true (default), the variable doesn't have to be used in the +expression itself but is also allowed if it's the first statement in the block +body. + +```go +someVariable := 1 +if anotherVariable { + someVariable++ +} +``` + +### `allow-whole-block` + +This is similar to `allow-first-in-block` but now allows the lack of whitespace +if it's used anywhere in the following block. + +```go +someVariable := 1 +if anotherVariable { + someFn(yetAnotherVariable) + + if stillNotSomeVariable { + someVariable++ + } +} +``` diff --git a/README.md b/README.md index ccf5dd4..77bda4e 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,10 @@ in more details. - ✅ **allow-first-in-block** - Allow cuddling a variable if it's used first in the immediate following block, even if the statement with the block doesn't use - the variable + the variable (see [Configuration](CHECKS.md#allow-first-in-block) for details) - ❌ **allow-whole-block** - Same as above, but allows cuddling if the variable is - used _anywhere_ in the following (or nested) block + used _anywhere_ in the following (or nested) block (see + [Configuration](CHECKS.md#allow-whole-block) for details) - **branch-max-lines** - If a block contains more than this number of lines the branch statement (e.g. `return`, `break`, `continue`) need to be separated by a whitespace (default 2) From 6f0a5ee5c5681e4e4bea401d79e0da949406417d Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 6 Jun 2025 16:33:08 +0200 Subject: [PATCH 111/112] Header is lowercase only --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 77bda4e..0b10b58 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# wsl - Whitespace Linter +# wsl - whitespace linter [![GitHub Actions](https://github.com/bombsimon/wsl/actions/workflows/go.yml/badge.svg)](https://github.com/bombsimon/wsl/actions/workflows/go.yml) [![Coverage Status](https://coveralls.io/repos/github/bombsimon/wsl/badge.svg?branch=main)](https://coveralls.io/github/bombsimon/wsl?branch=main) From 10bbbb042d98cf3b4f0ed8d995ea5e5e475c418d Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 6 Jun 2025 16:42:56 +0200 Subject: [PATCH 112/112] More docs updates --- CHECKS.md | 5 +++-- README.md | 39 +++++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/CHECKS.md b/CHECKS.md index 6a34086..a26f209 100644 --- a/CHECKS.md +++ b/CHECKS.md @@ -6,7 +6,7 @@ allowed and what's allowed. ## `assign` Assign (`foo := bar`) or re-assignments (`foo = bar`) should only be cuddled -with other assignments, declarations or increment/decrement. +with other assignments or increment/decrement. @@ -123,6 +123,7 @@ assignment increasing readability. > **NOTE** The fixer can't do smart adjustments and currently only add > whitespaces. +> This is tracked in [#21](https://github.com/bombsimon/wsl/issues/121)
BadGood
@@ -1061,7 +1062,7 @@ s = append(s, 2) 1 `a` is not used in append -2 `a` is not used in append +2 `b` is not used in append
BadGood
diff --git a/README.md b/README.md index 0b10b58..6ceea44 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,9 @@ the start and the end of blocks. ## Checks and configuration Each check can be disabled or enabled individually to the point where no checks -can be run. The idea with this is to attract more users. +can be run. The idea with this is to attract more users. Some checks have +configuration that affect how they work but most of them can only be turned on +or off. ### Checks @@ -30,7 +32,7 @@ For more details and examples, see [CHECKS](CHECKS.md). #### Built-ins and keywords - ✅ **assign** - Assignments should only be cuddled with other assignments, - declarations or increment/decrement + or increment/decrement - ✅ **branch** - Branch statement (`break`, `continue`, `fallthrough`, `goto`) should only be cuddled if the block is less than `n` lines where `n` is the value of [`branch-max-lines`](#configuration) @@ -51,25 +53,25 @@ For more details and examples, see [CHECKS](CHECKS.md). line above - ✅ **return** - Return should only be cuddled if the block is less than `n` lines where `n` is the value of [`branch-max-lines`](#configuration) -- ✅ **select** - Select should only be cuddled with a single variable used on the - line above -- ✅ **send** - Send should only be cuddled with a single variable used on the line - above -- ✅ **switch** - Switch should only be cuddled with a single variable used on the +- ✅ **select** - Select should only be cuddled with a single variable used on + the line above +- ✅ **send** - Send should only be cuddled with a single variable used on the line above +- ✅ **switch** - Switch should only be cuddled with a single variable used on + the line above - ✅ **type-switch** - Type switch should only be cuddled with a single variable used on the line above #### Specific `wsl` cases -- ✅ **append** - Only allow reassigning with `append` if the value being appended - exist on the line above -- ❌ **assign-exclusive** - Only allow cuddling either new variables or reassigning - of existing ones +- ✅ **append** - Only allow re-assigning with `append` if the value being + appended exist on the line above +- ❌ **assign-exclusive** - Only allow cuddling either new variables or + re-assigning of existing ones - ❌ **assign-expr** - Don't allow assignments to be cuddled with expressions, e.g. function calls -- ❌ **err** - Error checking must follow immediately after the error variable is - assigned +- ❌ **err** - Error checking must follow immediately after the error variable + is assigned - ✅ **leading-whitespace** - Disallow leading empty lines in blocks - ✅ **trailing-whitespace** - Disallow trailing empty lines in blocks @@ -78,11 +80,12 @@ For more details and examples, see [CHECKS](CHECKS.md). Other than enabling or disabling specific checks some checks can be configured in more details. -- ✅ **allow-first-in-block** - Allow cuddling a variable if it's used first in the - immediate following block, even if the statement with the block doesn't use - the variable (see [Configuration](CHECKS.md#allow-first-in-block) for details) -- ❌ **allow-whole-block** - Same as above, but allows cuddling if the variable is - used _anywhere_ in the following (or nested) block (see +- ✅ **allow-first-in-block** - Allow cuddling a variable if it's used first in + the immediate following block, even if the statement with the block doesn't + use the variable (see [Configuration](CHECKS.md#allow-first-in-block) for + details) +- ❌ **allow-whole-block** - Same as above, but allows cuddling if the variable + is used _anywhere_ in the following (or nested) block (see [Configuration](CHECKS.md#allow-whole-block) for details) - **branch-max-lines** - If a block contains more than this number of lines the branch statement (e.g. `return`, `break`, `continue`) need to be separated by