aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/bombsimon
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2020-07-04 11:12:55 +0200
committerDmitry Vyukov <dvyukov@google.com>2020-07-04 15:05:30 +0200
commitc7d7f10bdff703e4a3c0414e8a33d4e45c91eb35 (patch)
tree0dff0ee1f98dbfa3ad8776112053a450d176592b /vendor/github.com/bombsimon
parent9573094ce235bd9afe88f5da27a47dd6bcc1e13b (diff)
go.mod: vendor golangci-lint
Diffstat (limited to 'vendor/github.com/bombsimon')
-rw-r--r--vendor/github.com/bombsimon/wsl/v3/.gitignore70
-rw-r--r--vendor/github.com/bombsimon/wsl/v3/.travis.yml25
-rw-r--r--vendor/github.com/bombsimon/wsl/v3/LICENSE21
-rw-r--r--vendor/github.com/bombsimon/wsl/v3/README.md125
-rw-r--r--vendor/github.com/bombsimon/wsl/v3/go.mod12
-rw-r--r--vendor/github.com/bombsimon/wsl/v3/go.sum25
-rw-r--r--vendor/github.com/bombsimon/wsl/v3/wsl.go1122
7 files changed, 1400 insertions, 0 deletions
diff --git a/vendor/github.com/bombsimon/wsl/v3/.gitignore b/vendor/github.com/bombsimon/wsl/v3/.gitignore
new file mode 100644
index 000000000..1c8eba613
--- /dev/null
+++ b/vendor/github.com/bombsimon/wsl/v3/.gitignore
@@ -0,0 +1,70 @@
+
+# Created by https://www.gitignore.io/api/go,vim,macos
+
+### Go ###
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+### Go Patch ###
+/vendor/
+/Godeps/
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Vim ###
+# Swap
+[._]*.s[a-v][a-z]
+[._]*.sw[a-p]
+[._]s[a-rt-v][a-z]
+[._]ss[a-gi-z]
+[._]sw[a-p]
+
+# Session
+Session.vim
+
+# Temporary
+.netrwhist
+*~
+# Auto-generated tag files
+tags
+# Persistent undo
+[._]*.un~
+
+
+# End of https://www.gitignore.io/api/go,vim,macos
diff --git a/vendor/github.com/bombsimon/wsl/v3/.travis.yml b/vendor/github.com/bombsimon/wsl/v3/.travis.yml
new file mode 100644
index 000000000..5e2e26ed1
--- /dev/null
+++ b/vendor/github.com/bombsimon/wsl/v3/.travis.yml
@@ -0,0 +1,25 @@
+---
+language: go
+
+go:
+ - 1.13.x
+ - 1.12.x
+ - 1.11.x
+
+env:
+ global:
+ - GO111MODULE=on
+
+install:
+ - go get -v golang.org/x/tools/cmd/cover github.com/mattn/goveralls
+
+script:
+ - go test -v -covermode=count -coverprofile=coverage.out
+
+after_script:
+ - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
+
+notifications:
+ email: false
+
+# vim: set ts=2 sw=2 et:
diff --git a/vendor/github.com/bombsimon/wsl/v3/LICENSE b/vendor/github.com/bombsimon/wsl/v3/LICENSE
new file mode 100644
index 000000000..4dade6d1c
--- /dev/null
+++ b/vendor/github.com/bombsimon/wsl/v3/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 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
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/bombsimon/wsl/v3/README.md b/vendor/github.com/bombsimon/wsl/v3/README.md
new file mode 100644
index 000000000..c5068a8ac
--- /dev/null
+++ b/vendor/github.com/bombsimon/wsl/v3/README.md
@@ -0,0 +1,125 @@
+# 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)
+
+[![Build Status](https://travis-ci.org/bombsimon/wsl.svg?branch=master)](https://travis-ci.org/bombsimon/wsl)
+[![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.
+
+I think too much code out there is to cuddly and a bit too warm for it's own
+good, making it harder for other people to read and understand. The linter will
+warn about newlines in and around blocks, in the beginning of files and other
+places in the code.
+
+**I know 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
+
+### By `go get` (local installation)
+
+You can do that by using:
+
+```sh
+go get -u github.com/bombsimon/wsl/cmd/...
+```
+
+### By golangci-lint (CI automation)
+
+`wsl` is already integrated with
+[golangci-lint](https://github.com/golangci/golangci-lint). Please refer to the
+instructions there.
+
+## Usage
+
+How to use depends on how you install `wsl`.
+
+### With local binary
+
+The general command format for `wsl` is:
+
+```sh
+$ wsl [flags] <file1> [files...]
+$ wsl [flags] </path/to/package/...>
+
+# Examples
+
+$ wsl ./main.go
+$ wsl --no-test ./main.go
+$ wsl --allow-cuddle-declarations ./main.go
+$ wsl --no-test --allow-cuddle-declaration ./main.go
+$ wsl --no-test --allow-trailing-comment ./myProject/...
+```
+
+The "..." wildcard is not used like other `go` commands but instead can only
+be to a relative or absolute path.
+
+By default, the linter will run on `./...` which means all go files in the
+current path and all subsequent paths, including test files. To disable linting
+test files, use `-n` or `--no-test`.
+
+### By `golangci-lint` (CI automation)
+
+The recommended command is:
+
+```sh
+golangci-lint run --disable-all --enable wsl
+```
+
+For more information, please refer to
+[golangci-lint](https://github.com/golangci/golangci-lint)'s documentation.
+
+## 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).
+
+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).
+
+> **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.
+
+* [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 assginment 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)
+* [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)
diff --git a/vendor/github.com/bombsimon/wsl/v3/go.mod b/vendor/github.com/bombsimon/wsl/v3/go.mod
new file mode 100644
index 000000000..0c325eda1
--- /dev/null
+++ b/vendor/github.com/bombsimon/wsl/v3/go.mod
@@ -0,0 +1,12 @@
+module github.com/bombsimon/wsl/v3
+
+go 1.12
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
+ github.com/stretchr/testify v1.5.1
+ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
+ gopkg.in/yaml.v2 v2.2.8 // indirect
+)
diff --git a/vendor/github.com/bombsimon/wsl/v3/go.sum b/vendor/github.com/bombsimon/wsl/v3/go.sum
new file mode 100644
index 000000000..3bdb59247
--- /dev/null
+++ b/vendor/github.com/bombsimon/wsl/v3/go.sum
@@ -0,0 +1,25 @@
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+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/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+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/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/vendor/github.com/bombsimon/wsl/v3/wsl.go b/vendor/github.com/bombsimon/wsl/v3/wsl.go
new file mode 100644
index 000000000..3b4a4e9a1
--- /dev/null
+++ b/vendor/github.com/bombsimon/wsl/v3/wsl.go
@@ -0,0 +1,1122 @@
+package wsl
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io/ioutil"
+ "reflect"
+ "strings"
+)
+
+// Error reason strings
+const (
+ reasonMustCuddleErrCheck = "if statements that check an error must be cuddled with the statement that assigned the error"
+ reasonOnlyCuddleIfWithAssign = "if statements should only be cuddled with assignments"
+ reasonOnlyOneCuddle = "only one cuddle assignment allowed before if statement"
+ reasonOnlyCuddleWithUsedAssign = "if statements should only be cuddled with assignments used in the if statement itself"
+ reasonOnlyCuddle2LineReturn = "return statements should not be cuddled if block has more than two lines"
+ reasonMultiLineBranchCuddle = "branch statements should not be cuddled if block has more than two lines"
+ reasonAppendCuddledWithoutUse = "append only allowed to cuddle with appended value"
+ reasonAssignsCuddleAssign = "assignments should only be cuddled with other assignments"
+ reasonNeverCuddleDeclare = "declarations should never be cuddled"
+ reasonExpressionCuddledWithDeclOrRet = "expressions should not be cuddled with declarations or returns"
+ reasonExpressionCuddledWithBlock = "expressions should not be cuddled with blocks"
+ reasonExprCuddlingNonAssignedVar = "only cuddled expressions if assigning variable or using from line above"
+ reasonOneCuddleBeforeRange = "only one cuddle assignment allowed before range statement"
+ reasonRangeCuddledWithoutUse = "ranges should only be cuddled with assignments used in the iteration"
+ reasonOneCuddleBeforeDefer = "only one cuddle assignment allowed before defer statement"
+ reasonDeferCuddledWithOtherVar = "defer statements should only be cuddled with expressions on same variable"
+ reasonForWithoutCondition = "for statement without condition should never be cuddled"
+ reasonForWithMoreThanOneCuddle = "only one cuddle assignment allowed before for statement"
+ reasonForCuddledAssignWithoutUse = "for statements should only be cuddled with assignments used in the iteration"
+ reasonOneCuddleBeforeGo = "only one cuddle assignment allowed before go statement"
+ reasonGoFuncWithoutAssign = "go statements can only invoke functions assigned on line above"
+ reasonSwitchManyCuddles = "only one cuddle assignment allowed before switch statement"
+ reasonAnonSwitchCuddled = "anonymous switch statements should never be cuddled"
+ reasonSwitchCuddledWithoutUse = "switch statements should only be cuddled with variables switched"
+ reasonTypeSwitchTooCuddled = "only one cuddle assignment allowed before type switch statement"
+ reasonTypeSwitchCuddledWithoutUse = "type switch statements should only be cuddled with variables switched"
+ reasonBlockStartsWithWS = "block should not start with a whitespace"
+ reasonBlockEndsWithWS = "block should not end with a whitespace (or comment)"
+ reasonCaseBlockTooCuddly = "case block should end with newline at this size"
+)
+
+// 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"
+)
+
+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
+
+ // 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
+
+ // 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
+}
+
+// DefaultConfig returns default configuration
+func DefaultConfig() Configuration {
+ return Configuration{
+ StrictAppend: true,
+ AllowAssignAndCallCuddle: true,
+ AllowMultiLineAssignCuddle: true,
+ AllowTrailingComment: false,
+ AllowSeparatedLeadingComment: false,
+ ForceCuddleErrCheckAndAssign: false,
+ ForceCaseTrailingWhitespaceLimit: 0,
+ AllowCuddleWithCalls: []string{"Lock", "RLock"},
+ AllowCuddleWithRHS: []string{"Unlock", "RUnlock"},
+ ErrorVariableNames: []string{"err"},
+ }
+}
+
+// Result represents the result of one error.
+type Result struct {
+ FileName string
+ LineNumber int
+ Position token.Position
+ Reason string
+}
+
+// String returns the filename, line number and reason of a Result.
+func (r *Result) String() string {
+ return fmt.Sprintf("%s:%d: %s", r.FileName, r.LineNumber, r.Reason)
+}
+
+type Processor struct {
+ config Configuration
+ result []Result
+ warnings []string
+ fileSet *token.FileSet
+ file *ast.File
+}
+
+// NewProcessor will create a Processor.
+func NewProcessorWithConfig(cfg Configuration) *Processor {
+ return &Processor{
+ result: []Result{},
+ config: cfg,
+ }
+}
+
+// NewProcessor will create a Processor.
+func NewProcessor() *Processor {
+ return NewProcessorWithConfig(DefaultConfig())
+}
+
+// ProcessFiles takes a string slice with file names (full paths) and lints
+// them.
+// nolint: gocritic
+func (p *Processor) ProcessFiles(filenames []string) ([]Result, []string) {
+ for _, filename := range filenames {
+ data, err := ioutil.ReadFile(filename)
+ if err != nil {
+ panic(err)
+ }
+
+ p.process(filename, data)
+ }
+
+ return p.result, p.warnings
+}
+
+func (p *Processor) process(filename string, data []byte) {
+ fileSet := token.NewFileSet()
+ file, err := parser.ParseFile(fileSet, filename, data, parser.ParseComments)
+
+ // If the file is not parsable let's add a syntax error and move on.
+ if err != nil {
+ p.result = append(p.result, Result{
+ FileName: filename,
+ LineNumber: 0,
+ Reason: fmt.Sprintf("invalid syntax, file cannot be linted (%s)", err.Error()),
+ })
+
+ return
+ }
+
+ p.fileSet = fileSet
+ p.file = file
+
+ 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)
+ }
+ }
+}
+
+// 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() {
+ return
+ }
+
+ // Start by finding leading and trailing whitespaces.
+ p.findLeadingAndTrailingWhitespaces(ident, block, nil)
+
+ // Parse the block body contents.
+ p.parseBlockStatements(block.List)
+}
+
+// parseBlockStatements will parse all the statements found in the body of a
+// node. A list of Result is returned.
+// nolint: gocognit
+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]
+ 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
+ }
+
+ // 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.
+ var 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 assignedFirstInBlock []string
+
+ if firstBodyStatement != nil {
+ assignedFirstInBlock = p.findLHS(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
+ }
+
+ moreThanOneStatementAbove := func() bool {
+ if i < 2 {
+ return false
+ }
+
+ statementBeforePreviousStatement := statements[i-2]
+
+ return p.nodeStart(previousStatement)-1 == p.nodeEnd(statementBeforePreviousStatement)
+ }
+
+ 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
+ // }
+ // nolint: gocritic
+ if i == len(statements)-1 && i == 1 {
+ if p.nodeEnd(stmt)-p.nodeStart(previousStatement) <= 2 {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ // 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
+ }
+ }
+
+ 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 {
+ if checkingErrInitializedInline() {
+ continue
+ }
+
+ if atLeastOneInListsMatch(assignedOnLineAbove, p.config.ErrorVariableNames) {
+ p.addError(t.Pos(), reasonMustCuddleErrCheck)
+ }
+ }
+
+ continue
+ }
+
+ if len(assignedOnLineAbove) == 0 {
+ p.addError(t.Pos(), reasonOnlyCuddleIfWithAssign)
+ continue
+ }
+
+ if moreThanOneStatementAbove() {
+ p.addError(t.Pos(), reasonOnlyOneCuddle)
+ continue
+ }
+
+ if atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) {
+ continue
+ }
+
+ if atLeastOneInListsMatch(assignedOnLineAbove, assignedFirstInBlock) {
+ continue
+ }
+
+ p.addError(t.Pos(), reasonOnlyCuddleWithUsedAssign)
+ case *ast.ReturnStmt:
+ if isLastStatementInBlockOfOnlyTwoLines() {
+ continue
+ }
+
+ p.addError(t.Pos(), reasonOnlyCuddle2LineReturn)
+ case *ast.BranchStmt:
+ if isLastStatementInBlockOfOnlyTwoLines() {
+ continue
+ }
+
+ p.addError(t.Pos(), 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.addError(t.Pos(), reasonAppendCuddledWithoutUse)
+ }
+ }
+
+ continue
+ }
+
+ if _, ok := previousStatement.(*ast.AssignStmt); ok {
+ 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.addError(t.Pos(), reasonAssignsCuddleAssign)
+ case *ast.DeclStmt:
+ if !p.config.AllowCuddleDeclaration {
+ p.addError(t.Pos(), reasonNeverCuddleDeclare)
+ }
+ case *ast.ExprStmt:
+ switch previousStatement.(type) {
+ case *ast.DeclStmt, *ast.ReturnStmt:
+ p.addError(t.Pos(), reasonExpressionCuddledWithDeclOrRet)
+ case *ast.IfStmt, *ast.RangeStmt, *ast.SwitchStmt:
+ p.addError(t.Pos(), 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.addError(t.Pos(), reasonExprCuddlingNonAssignedVar)
+ }
+ case *ast.RangeStmt:
+ if moreThanOneStatementAbove() {
+ p.addError(t.Pos(), reasonOneCuddleBeforeRange)
+ continue
+ }
+
+ if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) {
+ if !atLeastOneInListsMatch(assignedOnLineAbove, assignedFirstInBlock) {
+ p.addError(t.Pos(), reasonRangeCuddledWithoutUse)
+ }
+ }
+ case *ast.DeferStmt:
+ if _, ok := previousStatement.(*ast.DeferStmt); ok {
+ // We may cuddle multiple defers to group logic.
+ continue
+ }
+
+ // Special treatment of deferring body closes after error checking
+ // according to best practices. See
+ // https://github.com/bombsimon/wsl/issues/31 which links to
+ // discussion about error handling after HTTP requests. This is hard
+ // coded and very specific but for now this is to be seen as a
+ // special case. What this does is that it *only* allows a defer
+ // statement with `Close` on the right hand side to be cuddled with
+ // an if-statement to support this:
+ // resp, err := client.Do(req)
+ // if err != nil {
+ // return err
+ // }
+ // defer resp.Body.Close()
+ if _, ok := previousStatement.(*ast.IfStmt); ok {
+ if atLeastOneInListsMatch(rightHandSide, []string{"Close"}) {
+ continue
+ }
+ }
+
+ if moreThanOneStatementAbove() {
+ p.addError(t.Pos(), reasonOneCuddleBeforeDefer)
+
+ 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
+ }
+
+ if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) {
+ p.addError(t.Pos(), reasonDeferCuddledWithOtherVar)
+ }
+ case *ast.ForStmt:
+ if len(rightAndLeftHandSide) == 0 {
+ p.addError(t.Pos(), reasonForWithoutCondition)
+
+ continue
+ }
+
+ if moreThanOneStatementAbove() {
+ p.addError(t.Pos(), reasonForWithMoreThanOneCuddle)
+
+ 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, assignedFirstInBlock) {
+ p.addError(t.Pos(), reasonForCuddledAssignWithoutUse)
+ }
+ }
+ case *ast.GoStmt:
+ if _, ok := previousStatement.(*ast.GoStmt); ok {
+ continue
+ }
+
+ if moreThanOneStatementAbove() {
+ p.addError(t.Pos(), reasonOneCuddleBeforeGo)
+
+ continue
+ }
+
+ if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) {
+ p.addError(t.Pos(), reasonGoFuncWithoutAssign)
+ }
+ case *ast.SwitchStmt:
+ if moreThanOneStatementAbove() {
+ p.addError(t.Pos(), reasonSwitchManyCuddles)
+
+ continue
+ }
+
+ if !atLeastOneInListsMatch(rightAndLeftHandSide, assignedOnLineAbove) {
+ if len(rightAndLeftHandSide) == 0 {
+ p.addError(t.Pos(), reasonAnonSwitchCuddled)
+ } else {
+ p.addError(t.Pos(), reasonSwitchCuddledWithoutUse)
+ }
+ }
+ case *ast.TypeSwitchStmt:
+ if moreThanOneStatementAbove() {
+ p.addError(t.Pos(), reasonTypeSwitchTooCuddled)
+
+ 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, assignedFirstInBlock) {
+ p.addError(t.Pos(), 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]
+ }
+ }
+ }
+
+ 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.KeyValueExpr, *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)...)
+ default:
+ if x, ok := maybeX(t); ok {
+ return p.findRHS(x)
+ }
+
+ p.addWarning(warnUnknownRHS, t.Pos(), t)
+ }
+
+ return rhs
+}
+
+func (p *Processor) findBlockStmt(node ast.Node) []*ast.BlockStmt {
+ var blocks []*ast.BlockStmt
+
+ switch t := node.(type) {
+ case *ast.AssignStmt:
+ for _, x := range t.Rhs {
+ blocks = append(blocks, p.findBlockStmt(x)...)
+ }
+ case *ast.CallExpr:
+ blocks = append(blocks, p.findBlockStmt(t.Fun)...)
+ 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)...)
+ }
+
+ 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.
+// nolint: gocognit
+func (p *Processor) findLeadingAndTrailingWhitespaces(ident *ast.Ident, stmt, nextStatement ast.Node) {
+ var (
+ allowedLinesBeforeFirstStatement = 1
+ commentMap = ast.NewCommentMap(p.fileSet, stmt, p.file.Comments)
+ blockStatements []ast.Stmt
+ blockStartLine int
+ blockEndLine int
+ blockStartPos token.Pos
+ blockEndPos token.Pos
+ )
+
+ // 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
+ case *ast.CommClause:
+ blockStatements = t.Body
+ blockStartPos = t.Colon
+ default:
+ p.addWarning(warnWSNodeTypeNotImplemented, stmt.Pos(), stmt)
+
+ return
+ }
+
+ // Ignore empty blocks even if they have newlines or just comments.
+ if len(blockStatements) < 1 {
+ 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]
+ seenCommentGroups = 0
+ )
+
+ // Get the comment related to the first statement, we do allow commends in
+ // the beginning of a block before the first statement.
+ if c, ok := commentMap[firstStatement]; ok {
+ for _, commentGroup := range c {
+ // If the comment group is on the same line as the block start
+ // (LBrace) we should not consider it.
+ if p.nodeStart(commentGroup) == blockStartLine {
+ 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 store number of seen comment groups because we allow multiple
+ // groups with a newline between them.
+ seenCommentGroups++
+
+ // Support both /* multiline */ and //single line comments
+ for _, c := range commentGroup.List {
+ allowedLinesBeforeFirstStatement += len(strings.Split(c.Text, "\n"))
+ }
+ }
+ }
+
+ // If we have multiple groups, add support for newline between each group.
+ if p.config.AllowSeparatedLeadingComment {
+ if seenCommentGroups > 1 {
+ allowedLinesBeforeFirstStatement += seenCommentGroups - 1
+ }
+ }
+
+ if p.nodeStart(firstStatement) != blockStartLine+allowedLinesBeforeFirstStatement {
+ p.addError(
+ blockStartPos,
+ reasonBlockStartsWithWS,
+ )
+ }
+
+ // If the blockEndLine is not 0 we're a regular block (not case).
+ if blockEndLine != 0 {
+ if p.config.AllowTrailingComment {
+ if lastComment, ok := commentMap[lastStatement]; ok {
+ var (
+ lastCommentGroup = lastComment[len(lastComment)-1]
+ lastCommentLine = lastCommentGroup.List[len(lastCommentGroup.List)-1]
+ countNewlines = 0
+ )
+
+ countNewlines += len(strings.Split(lastCommentLine.Text, "\n"))
+
+ // No newlines between trailing comments and end of block.
+ if p.nodeStart(lastCommentLine)+countNewlines != blockEndLine-1 {
+ return
+ }
+ }
+ }
+
+ if p.nodeEnd(lastStatement) != blockEndLine-1 && !isExampleFunc(ident) {
+ p.addError(blockEndPos, reasonBlockEndsWithWS)
+ }
+
+ 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 n := nextStatement.(type) {
+ case *ast.CaseClause:
+ blockEndPos = n.Case
+ case *ast.CommClause:
+ blockEndPos = n.Case
+ default:
+ // No more cases
+ return
+ }
+
+ blockEndLine = p.fileSet.Position(blockEndPos).Line - 1
+
+ var (
+ blockSize = blockEndLine - blockStartLine
+ caseTrailingCommentLines int
+ )
+
+ // TODO: I don't know what comments are bound to in cases. For regular
+ // blocks the last comment is bound to the last statement but for cases
+ // they are bound to the case clause expression. This will however get us all
+ // comments and depending on the case expression this gets tricky.
+ //
+ // To handle this I get the comment map from the current statement (the case
+ // itself) and iterate through all groups and all comment within all groups.
+ // I then get the comments after the last statement but before the next case
+ // clause and just map each line of comment that way.
+ for _, commentGroups := range commentMap {
+ for _, commentGroup := range commentGroups {
+ for _, comment := range commentGroup.List {
+ commentLine := p.fileSet.Position(comment.Pos()).Line
+
+ // Ignore comments before the last statement.
+ if commentLine <= p.nodeStart(lastStatement) {
+ continue
+ }
+
+ // Ignore comments after the end of this case.
+ if commentLine > blockEndLine {
+ continue
+ }
+
+ // This allows /* multiline */ comments with newlines as well
+ // as regular (//) ones
+ caseTrailingCommentLines += len(strings.Split(comment.Text, "\n"))
+ }
+ }
+ }
+
+ hasTrailingWhitespace := p.nodeEnd(lastStatement)+caseTrailingCommentLines != blockEndLine
+
+ // If the force trailing limit is configured and we don't end with a newline.
+ if p.config.ForceCaseTrailingWhitespaceLimit > 0 && !hasTrailingWhitespace {
+ // Check if the block size is too big to miss the newline.
+ if blockSize >= p.config.ForceCaseTrailingWhitespaceLimit {
+ p.addError(lastStatement.Pos(), reasonCaseBlockTooCuddly)
+ }
+ }
+}
+
+func isExampleFunc(ident *ast.Ident) bool {
+ return ident != nil && strings.HasPrefix(ident.Name, "Example")
+}
+
+func (p *Processor) nodeStart(node ast.Node) int {
+ return p.fileSet.Position(node.Pos()).Line
+}
+
+func (p *Processor) nodeEnd(node ast.Node) int {
+ return p.fileSet.Position(node.End()).Line
+}
+
+// Add an error for the file and line number for the current token.Pos with the
+// given reason.
+func (p *Processor) addError(pos token.Pos, reason string) {
+ position := p.fileSet.Position(pos)
+
+ p.result = append(p.result, Result{
+ FileName: position.Filename,
+ LineNumber: position.Line,
+ Position: position,
+ Reason: reason,
+ })
+}
+
+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),
+ )
+}