From 3e09174bd2cd9a795da31dc1b36ed98af1102dd3 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Fri, 4 Oct 2019 22:03:40 +0200 Subject: [PATCH] Add WSL linter (#771) * Add WSL linter * Use v1.0.0 tag for wsl * Don't add specific test file skip, use mutex to add errors * Fix goimports error * Add more tests for WSL, bump WSL version * Fix bad go.sum (go mod tidy) --- README.md | 3 + go.mod | 3 +- go.sum | 4 + pkg/golinters/wsl.go | 63 ++ pkg/lint/lintersdb/manager.go | 3 + test/testdata/wsl.go | 80 +++ vendor/github.com/bombsimon/wsl/.gitignore | 70 ++ vendor/github.com/bombsimon/wsl/.travis.yml | 25 + vendor/github.com/bombsimon/wsl/LICENSE | 21 + vendor/github.com/bombsimon/wsl/README.md | 594 ++++++++++++++++ vendor/github.com/bombsimon/wsl/go.mod | 11 + vendor/github.com/bombsimon/wsl/go.sum | 24 + vendor/github.com/bombsimon/wsl/wsl.go | 727 ++++++++++++++++++++ vendor/gopkg.in/yaml.v2/decode.go | 38 + vendor/gopkg.in/yaml.v2/resolve.go | 2 +- vendor/gopkg.in/yaml.v2/scannerc.go | 16 + vendor/modules.txt | 4 +- 17 files changed, 1685 insertions(+), 3 deletions(-) create mode 100644 pkg/golinters/wsl.go create mode 100644 test/testdata/wsl.go create mode 100644 vendor/github.com/bombsimon/wsl/.gitignore create mode 100644 vendor/github.com/bombsimon/wsl/.travis.yml create mode 100644 vendor/github.com/bombsimon/wsl/LICENSE create mode 100644 vendor/github.com/bombsimon/wsl/README.md create mode 100644 vendor/github.com/bombsimon/wsl/go.mod create mode 100644 vendor/github.com/bombsimon/wsl/go.sum create mode 100644 vendor/github.com/bombsimon/wsl/wsl.go diff --git a/README.md b/README.md index 6ed42704..0b8fbcef 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,7 @@ stylecheck: Stylecheck is a replacement for golint [fast: true, auto-fix: false] unconvert: Remove unnecessary type conversions [fast: true, auto-fix: false] unparam: Reports unused function parameters [fast: true, auto-fix: false] whitespace: Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true] +wsl: Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false] ``` Pass `-E/--enable` to enable linter and `-D/--disable` to disable: @@ -469,6 +470,7 @@ golangci-lint help linters - [godox](https://github.com/matoous/godox) - Tool for detection of FIXME, TODO and other comment keywords - [funlen](https://github.com/ultraware/funlen) - Tool for detection of long functions - [whitespace](https://github.com/ultraware/whitespace) - Tool for detection of leading and trailing whitespace +- [wsl](https://github.com/bombsimon/wsl) - Whitespace Linter - Forces you to use empty lines! ## Configuration @@ -1123,6 +1125,7 @@ Thanks to developers and authors of used linters: - [leighmcculloch](https://github.com/leighmcculloch) - [matoous](https://github.com/matoous) - [ultraware](https://github.com/ultraware) +- [bombsimon](https://github.com/bombsimon) ## Changelog diff --git a/go.mod b/go.mod index 79472285..d686ca3f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.12 require ( github.com/OpenPeeDeeP/depguard v1.0.1 + github.com/bombsimon/wsl v1.0.1 github.com/fatih/color v1.7.0 github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db github.com/go-lintpack/lintpack v0.5.2 @@ -40,7 +41,7 @@ require ( github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517 github.com/valyala/quicktemplate v1.2.0 golang.org/x/tools v0.0.0-20190912215617-3720d1ec3678 - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.2.4 honnef.co/go/tools v0.0.1-2019.2.3 mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect diff --git a/go.sum b/go.sum index 2fb86c66..871efbe1 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bombsimon/wsl v1.0.1 h1:GkrpOj7ajds8re6EJXN+k6UtatYSktupigQ2ZfOUIOU= +github.com/bombsimon/wsl v1.0.1/go.mod h1:DSRwCD8c7NecM11/LnZcTS8nS8OND5ybj04DWM4l8mc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -308,6 +310,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/pkg/golinters/wsl.go b/pkg/golinters/wsl.go new file mode 100644 index 00000000..f037de24 --- /dev/null +++ b/pkg/golinters/wsl.go @@ -0,0 +1,63 @@ +package golinters + +import ( + "sync" + + "github.com/bombsimon/wsl" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +const ( + name = "wsl" +) + +// NewWSL returns a new WSL linter. +func NewWSL() *goanalysis.Linter { + var ( + issues []result.Issue + mu = sync.Mutex{} + analyzer = &analysis.Analyzer{ + Name: goanalysis.TheOnlyAnalyzerName, + Doc: goanalysis.TheOnlyanalyzerDoc, + } + ) + + return goanalysis.NewLinter( + name, + "Whitespace Linter - Forces you to use empty lines!", + []*analysis.Analyzer{analyzer}, + nil, + ).WithContextSetter(func(lintCtx *linter.Context) { + analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { + files := []string{} + + for _, file := range pass.Files { + files = append(files, pass.Fset.Position(file.Pos()).Filename) + } + + wslErrors, _ := wsl.ProcessFiles(files) + if len(wslErrors) == 0 { + return nil, nil + } + + mu.Lock() + defer mu.Unlock() + + for _, err := range wslErrors { + issues = append(issues, result.Issue{ + FromLinter: name, + Pos: err.Position, + Text: err.Reason, + }) + } + + return nil, nil + } + }).WithIssuesReporter(func(*linter.Context) []result.Issue { + return issues + }).WithLoadMode(goanalysis.LoadModeSyntax) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 89b4b7af..700552c8 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -204,6 +204,9 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle). WithAutoFix(). WithURL("https://github.com/ultraware/whitespace"), + linter.NewConfig(golinters.NewWSL()). + WithPresets(linter.PresetStyle). + WithURL("https://github.com/bombsimon/wsl"), } isLocalRun := os.Getenv("GOLANGCI_COM_RUN") == "" diff --git a/test/testdata/wsl.go b/test/testdata/wsl.go new file mode 100644 index 00000000..7659a5fb --- /dev/null +++ b/test/testdata/wsl.go @@ -0,0 +1,80 @@ +//args: -Ewsl +//config: linters-settings.wsl.tests=1 +package testdata + +import ( + "context" + "fmt" +) + +func main() { + var ( + y = 0 + ) + if y < 1 { // ERROR "if statements should only be cuddled with assignments" + fmt.Println("tight") + } + + thisIsNotUsedInIf := true + if 2 > 1 { // ERROR "if statements should only be cuddled with assignments used in the if statement itself" + return + } + + one := 1 + two := 2 + three := 3 + if three == 3 { // ERROR "only one cuddle assignment allowed before if statement" + fmt.Println("too many cuddled assignments", one, two, thisIsNotUsedInIf) + } + + var a = "a" + var b = "b" // ERROR "declarations should never be cuddled" + + if true { + return + } + if false { // ERROR "if statements should only be cuddled with assignments" + return + } + + for i := range make([]int, 10) { + fmt.Println(i) + fmt.Println(i + i) + continue // ERROR "branch statements should not be cuddled if block has more than two lines" + } + + assignOne := a + fmt.Println(assignOne) + assignTwo := b // ERROR "assignments should only be cuddled with other assignments" + fmt.Println(assignTwo) + + _, cf1 := context.WithCancel(context.Background()) + _, cf2 := context.WithCancel(context.Background()) + defer cf1() // ERROR "only one cuddle assignment allowed before defer statement" + defer cf2() +} + +func f1() int { + x := 1 + return x +} + +func f2() int { + x := 1 + y := 3 + return x + y // ERROR "return statements should not be cuddled if block has more than two lines" +} + +func f3() int { + sum := 0 + for _, v := range []int{2, 4, 8} { + sum += v + } + + notSum := 0 + for _, v := range []int{1, 2, 4} { // ERROR "ranges should only be cuddled with assignments used in the iteration" + sum += v + } + + return sum + notSum +} diff --git a/vendor/github.com/bombsimon/wsl/.gitignore b/vendor/github.com/bombsimon/wsl/.gitignore new file mode 100644 index 00000000..1c8eba61 --- /dev/null +++ b/vendor/github.com/bombsimon/wsl/.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/.travis.yml b/vendor/github.com/bombsimon/wsl/.travis.yml new file mode 100644 index 00000000..d84a5d6c --- /dev/null +++ b/vendor/github.com/bombsimon/wsl/.travis.yml @@ -0,0 +1,25 @@ +--- +language: go + +go: + - "1.13" + - "1.12" + - "1.11" + +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/LICENSE b/vendor/github.com/bombsimon/wsl/LICENSE new file mode 100644 index 00000000..4dade6d1 --- /dev/null +++ b/vendor/github.com/bombsimon/wsl/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/README.md b/vendor/github.com/bombsimon/wsl/README.md new file mode 100644 index 00000000..7b84ee51 --- /dev/null +++ b/vendor/github.com/bombsimon/wsl/README.md @@ -0,0 +1,594 @@ +# 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. + +## Usage + +Install by using `go get -u github.com/bombsimon/wsl/cmd/...`. + +Run with `wsl [--no-test] [files...]` or `wsl ./package/...`. 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`. + +## Rules + +Note that this linter doesn't take in consideration the issues that will be +fixed with `gofmt` so ensure that the code is properly formatted. + +### Never use empty lines + +Even though this linter was built to **promote** the usage of empty lines, there +are a few places where they should never be used. + +Never use empty lines in the start or end of a block! + +**Don't** + +```go +if someBooleanValue { + + fmt.Println("i like starting newlines") +} + +if someOtherBooleanValue { + fmt.Println("also trailing") + +} + +switch { + +case 1: + fmt.Println("switch is also a block") +} + +switch { +case 1: + + fmt.Println("not in a case") +case 2: + fmt.Println("or at the end") + +} + +func neverNewlineAfterReturn() { + return true + +} + +func notEvenWithComments() { + return false + // I just forgot to say this... +} +``` + +**Do** + +```go +if someBooleanValue { + fmt.Println("this is tight and nice") +} + +switch { +case 1: + fmt.Println("no blank lines here") +case 2: + // Comments are fine here! + fmt.Println("or here") +} + +func returnCuddleded() { + // My comment goes above the last statement! + return true +} +``` + +### Use empty lines + +There's an easy way to improve logic and readability by enforcing whitespaces at +the right places. Usually this is around blocks and after declarations. + +#### If + +If statements should only be cuddled with assignments/declarations of variables +used in the condition and only one assignment should be cuddled. Never cuddle if +with anything but assignments. + +**Don't** + +```go +notConditional := "x" +if somethingElse == "y" { + fmt.Println("what am i checking?") +} + +first := 1 +second := 2 +third := 3 +forever := 4 +if forever { + return true +} + +if false { + fmt.Println("first if") +} +if true { + fmt.Println("second if is cuddled") +} +``` + +**Do** + +```go +val, err := SomeThing() +if err != nil { + // err is assigned on line above +} + +first := 1 +second := 2 +third := 3 +forever := 4 + +if forever > 3 { + return fmt.Sprintf("group multiple assignments away from if") +} + +// Or separate from your condition. +first := 1 +second := 2 +third := 3 + +forever := 4 +if forever > 3 { + return fmt.Sprintf("group multiple assignments away from if") +} + +if false { + // This is one statement +} + +if true { + // This is another one, don't cuddled them! +} +``` + +#### Return + +Return should be placed on a separate line from other statement unless the block +consists of only two lines (including the return). + +**Don't** + +```go +doSomething() +add := 1+2 +fmt.Sprintf(add) +return false + +if true { + stmt.X = true + stmt.Y = false + return true +} +``` + +**Do** + +```go +doSomething() + +add := 1+2 +fmt.Sprintf(add)) + +return false + +if true { + stmt.X = "only one line without return, may cuddled" + return true +} + +if thisWorksToo { + whitespace := true + + return false +} +``` + +#### Branch statement + +The same rule as for return + +**Don't** + +```go +for i := range make([]int, 5) { + if i > 2 { + sendToOne(i) + sendToSecond(i) + continue + } +} +``` + +**Do** + +```go +for i := range make([]int, 5) { + if i > 2 { + sendToOne(i) + sendToSecond(i) + + continue + } + + if statement == "is short" { + sendToOne(i) + break + } +} +``` + +#### Assignment + +Assignments may only be cuddled with other assignments. + +**Don't** + +```go +assignOne := "one" +callFunc(assignOne) +assignTwo := "two") +callFunc(assignTwo) + +if true { + return +} +assigningClose := "bad" + +var x = 2 +y := 3 +``` + +**Do** + +```go +assignOne := "one" +assignTwo := "two") + +callFunc(assignOne) +callFunc(assignTwo) + +// Or group assignment and call by usage. +assignOne := "one" +callFunc(assignOne) + +assignTwo := "two") +callFunc(assignTwo) + +if true { + return +} + +notAssigningClose := "not bad" + +var x = 2 + +y := 3 +``` + +#### Declarations + +Declarations should never be cuddled with anything, not even other declarations. + +**Don't** + +```go +var x int +var y int + +z := 2 +var a +``` + +**Do** + +```go +// Group declarations, they'll align nice and tidy! +var ( + x int + y int +) + +z := 2 + +var a +``` + +#### Expressions + +Expressions (function calls) may never be cuddled with declarations or return +statements. Expressions may also not be cuddled with assignments if not passed +to the expression func. + +**Don't** + +```go +var a bool +fmt.Println(a) + +foo := true +someFunc(false) +``` + +**Do** + +```go +var b bool + +fmt.Println(b) + +foo := true +someFunc(foo) + +bar := false + +someFunc(true) +``` + +#### Ranges + +Range statements may only be cuddled with assignments that are used in the +range. Just like if statements this only applies if it's a single assignment +cuddled and not multiple. + +Ranges may also be cuddled with assignments that are used directly in the block +as first statement. + +**Don't** + +```go +noRangeList := []string{"a", "b", "c"} +for _, x := range anotherList { + fmt.Println(x) +} + +oneList := []int{1, 2, 3} +twoList := []int{4, 5, 6} +for i := range twoList { + fmt.Println("too much assignments!") +} + +myCount := 0 +for _, v := range aList { + fmt.Sprintf("first statement doesn't use assignment") +} +``` + +**Do** + +```go +rangeList := []string{"a", "b", "c"} +for _, x := range rangeList { + fmt.Println(x) +} + +oneList := []int{1, 2, 3} + +twoList := []int{4, 5, 6} +for i := range twoList { + fmt.Println("too much assignments!") +} + +myCount := 0 +for _, v := range aList { + myCount += v + + fmt.Sprintf("first statement uses cuddled assignment") +} +``` + +#### Defer + +Defer is almost handled like return statements but there are cases where +grouping defer statements with each other or expression calls may improve +readability. + +Defer statements may be cuddled with other defer statements as many times as you +like. It may also be cuddled with assignments above or expression variables on +the line above. + +**Don't** + +```go +first := getFirst() +defer first.Close() +second := getSecond() // This will fail +defer second.Close() + +first := getFirst() +second := getSecond() +defer first.Close() // Too many assignments above +defer second.Close() + +m1.Lock() +defer m2.RUnlock() // Not the expression above +``` + +**Do** + +```go +first := getFirst() +second := getSecond() + +defer first.Close() +defer second.Close() + +// Or group by usage. +first := getFirst() +defer first.Close() + +second := getSecond() +defer second.Close() + +m.Lock() +defer m.Unlock() +``` + +#### For loops + +For statements works similar like ranges except that anonymous (infinite) loops +may never be cuddled. Just like the range statement, variables used in the for +or in first statement in the body may be cuddled. + +**Don't** + +```go +t := true +for notT { + fmt.Println("where's t used?") +} + +n := 0 +m := 1 +for x < 100 { + n += x // m not used in for or body +} + +n := 1 +for { + fmt.Println("never cuddled for without condition") +} +``` + +**Do** + +```go +t := true +for t { + fmt.Println("t used in for") +} + +n := 0 +for x < 100 { + n += x // n used in first block statement. +} + +n := 1 + +for { + fmt.Println("never cuddled for without condition") +} +``` + +#### Go + +Go routines may only be executed if there's a maximum of one assignments above +and that assignment is used in the expression. + +**Don't** + +```go +first := func() {} +second := func() {} +go second() + +notUsed := func() {} +go first() + +x := "1" +go func() { + fmt.Println("where's x used!=") +}() +``` + +**Do** + +```go +first := func() {} +go first() + +notUsed := func() {} + +first := func() {} +go first() +``` + +#### Switch and Type switch + +The same rules applies for switch and type switch statements with the exception +of anonymous type switches. That means type switches where a new variable is not +assigned. It's also allowed to cuddled type switches with variables used if it's +used as the first argument in the first case. + +Type switches may only be cuddled with one assignment above and if that +assignment is used in the switch. + +**Don't** + +```go +notSome := SomeInt() +switch some { +case 1: + fmt.Println("1") +default: + fmt.Println("not 1") +} + +notSwitched := SomeInt() +switch { +case 1 > 2: + fmt.Println("whitespace between assignments") +} + +n := 0 +switch v := some.(type): +case typeOne: + x := v.X // n not used in switch or body +case typeTwo: + x := v.X +} +``` + +**Do** + +```go +some := SomeInt() +switch some { +case 1: + fmt.Println("1") +default: + fmt.Println("not 1") +} + +notSwitched := SomeInt() + +switch { +case 1 > 2: + fmt.Println("whitespace between assignments") +} + +n := 0 +switch v := some.(type): +case typeOne: + n = v.X // n is used first in block, OK to cuddle +case typeTwo: + n = v.Y +} +``` diff --git a/vendor/github.com/bombsimon/wsl/go.mod b/vendor/github.com/bombsimon/wsl/go.mod new file mode 100644 index 00000000..20b38773 --- /dev/null +++ b/vendor/github.com/bombsimon/wsl/go.mod @@ -0,0 +1,11 @@ +module github.com/bombsimon/wsl + +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/kr/pretty v0.1.0 // indirect + github.com/stretchr/testify v1.4.0 + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/yaml.v2 v2.2.4 // indirect +) + +go 1.11 diff --git a/vendor/github.com/bombsimon/wsl/go.sum b/vendor/github.com/bombsimon/wsl/go.sum new file mode 100644 index 00000000..1033a6ca --- /dev/null +++ b/vendor/github.com/bombsimon/wsl/go.sum @@ -0,0 +1,24 @@ +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/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/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.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/bombsimon/wsl/wsl.go b/vendor/github.com/bombsimon/wsl/wsl.go new file mode 100644 index 00000000..7b74acbb --- /dev/null +++ b/vendor/github.com/bombsimon/wsl/wsl.go @@ -0,0 +1,727 @@ +package wsl + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "reflect" +) + +type result struct { + FileName string + LineNumber int + Position token.Position + Reason string +} + +type processor struct { + result []result + warnings []string + fileSet *token.FileSet + file *ast.File +} + +// ProcessFiles takes a string slice with file names (full paths) and lints +// them. +func ProcessFiles(filenames []string) ([]result, []string) { + p := NewProcessor() + + // Iterate over all files. + for _, filename := range filenames { + data, err := ioutil.ReadFile(filename) + if err != nil { + panic(err) + } + + p.process(filename, data) + } + + return p.result, p.warnings +} + +// NewProcessor will create a processor. +func NewProcessor() *processor { + return &processor{ + result: []result{}, + } +} + +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.Body) + case *ast.GenDecl: + // `go fmt` will handle proper spacing for GenDecl such as imports, + // constants etc. + default: + p.addWarning("type not implemented", 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(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(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. +func (p *processor) parseBlockStatements(statements []ast.Stmt) { + for i, stmt := range statements { + // TODO: How to tell when and where func literals may exist to enforce + // linting. + if as, isAssignStmt := stmt.(*ast.AssignStmt); isAssignStmt { + for _, rhs := range as.Rhs { + if fl, isFuncLit := rhs.(*ast.FuncLit); isFuncLit { + p.parseBlockBody(fl.Body) + } + } + } + + firstBodyStatement := p.firstBodyStatement(i, statements) + + // First statement, nothing to do. + if i == 0 { + continue + } + + previousStatement := statements[i-1] + + // If the last statement didn't end one line above the current statement + // we know we're not cuddled so just move on. + if p.nodeEnd(previousStatement) != p.nodeStart(stmt)-1 { + continue + } + + // We know we're cuddled, 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 + + // Ensure previous line is not a multi line assignment and if not get + // all assigned variables. + if p.nodeStart(previousStatement) == p.nodeStart(stmt)-1 { + 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) + } + + lhs := p.findLhs(stmt) + rhs := p.findRhs(stmt) + all := append(lhs, rhs...) + + /* + DEBUG: + fmt.Println("LHS: ", lhs) + fmt.Println("RHS: ", rhs) + fmt.Println("Assigned above: ", assignedOnLineAbove) + fmt.Println("Assigned first: ", assignedFirstInBlock) + */ + + moreThanOneStatementAbove := func() bool { + if i < 2 { + return false + } + + statementBeforePreviousStatement := statements[i-2] + if p.nodeStart(previousStatement)-1 == p.nodeEnd(statementBeforePreviousStatement) { + return true + } + + return false + } + + 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 i == len(statements)-1 && i == 1 { + if p.nodeEnd(stmt)-p.nodeStart(previousStatement) <= 2 { + return true + } + } + + return false + } + + switch t := stmt.(type) { + case *ast.IfStmt: + if len(assignedOnLineAbove) == 0 { + p.addError(t.Pos(), "if statements should only be cuddled with assignments") + + continue + } + + if moreThanOneStatementAbove() { + p.addError(t.Pos(), "only one cuddle assignment allowed before if statement") + + continue + } + + if !atLeastOneInListsMatch(all, assignedOnLineAbove) { + if !atLeastOneInListsMatch(assignedOnLineAbove, assignedFirstInBlock) { + p.addError(t.Pos(), "if statements should only be cuddled with assignments used in the if statement itself") + } + } + case *ast.ReturnStmt: + if isLastStatementInBlockOfOnlyTwoLines() { + continue + } + + p.addError(t.Pos(), "return statements should not be cuddled if block has more than two lines") + case *ast.BranchStmt: + if isLastStatementInBlockOfOnlyTwoLines() { + continue + } + + p.addError(t.Pos(), "branch statements should not be cuddled if block has more than two lines") + case *ast.AssignStmt: + // append is usually an assignment but should not be allowed to be + // cuddled with anything not appended. + if len(rhs) > 0 && rhs[len(rhs)-1] == "append" { + if !atLeastOneInListsMatch(assignedOnLineAbove, rhs) { + p.addError(t.Pos(), "append only allowed to cuddle with appended value") + } + } + + if _, ok := previousStatement.(*ast.AssignStmt); ok { + continue + } + + p.addError(t.Pos(), "assignments should only be cuddled with other assignments") + case *ast.DeclStmt: + p.addError(t.Pos(), "declarations should never be cuddled") + case *ast.ExprStmt: + switch previousStatement.(type) { + case *ast.DeclStmt, *ast.ReturnStmt: + p.addError(t.Pos(), "expressions should not be cuddled with declarations or returns") + } + + // If we assigned variables on the line above but didn't use them in + // this expression we there should probably be a newline between + // them. + if len(assignedOnLineAbove) > 0 && !atLeastOneInListsMatch(all, assignedOnLineAbove) { + p.addError(t.Pos(), "only cuddled expressions if assigning variable or using from line above") + } + case *ast.RangeStmt: + if moreThanOneStatementAbove() { + p.addError(t.Pos(), "only one cuddle assignment allowed before range statement") + + continue + } + + if !atLeastOneInListsMatch(all, assignedOnLineAbove) { + if !atLeastOneInListsMatch(assignedOnLineAbove, assignedFirstInBlock) { + p.addError(t.Pos(), "ranges should only be cuddled with assignments used in the iteration") + } + } + case *ast.DeferStmt: + if _, ok := previousStatement.(*ast.DeferStmt); ok { + // We may cuddle multiple defers to group logic. + continue + } + + if moreThanOneStatementAbove() { + p.addError(t.Pos(), "only one cuddle assignment allowed before defer statement") + + 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(rhs, previousRhs) { + continue + } + + if !atLeastOneInListsMatch(all, assignedOnLineAbove) { + p.addError(t.Pos(), "defer statements should only be cuddled with expressions on same variable") + } + case *ast.ForStmt: + if len(all) == 0 { + p.addError(t.Pos(), "for statement without condition should never be cuddled") + + continue + } + + if moreThanOneStatementAbove() { + p.addError(t.Pos(), "only one cuddle assignment allowed before for statement") + + 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(all, assignedOnLineAbove) { + if !atLeastOneInListsMatch(assignedOnLineAbove, assignedFirstInBlock) { + p.addError(t.Pos(), "for statements should only be cuddled with assignments used in the iteration") + } + } + case *ast.GoStmt: + if moreThanOneStatementAbove() { + p.addError(t.Pos(), "only one cuddle assignment allowed before go statement") + + continue + } + + if !atLeastOneInListsMatch(all, assignedOnLineAbove) { + p.addError(t.Pos(), "go statements can only invoke functions assigned on line above") + } + case *ast.SwitchStmt: + if moreThanOneStatementAbove() { + p.addError(t.Pos(), "only one cuddle assignment allowed before switch statement") + + continue + } + + if !atLeastOneInListsMatch(all, assignedOnLineAbove) { + if len(all) == 0 { + p.addError(t.Pos(), "anonymous switch statements should never be cuddled") + } else { + p.addError(t.Pos(), "switch statements should only be cuddled with variables switched") + } + } + case *ast.TypeSwitchStmt: + if moreThanOneStatementAbove() { + p.addError(t.Pos(), "only one cuddle assignment allowed before type switch statement") + + continue + } + + // Allowed to type assert on variable assigned on line above. + if !atLeastOneInListsMatch(rhs, assignedOnLineAbove) { + // Allow type assertion on variables used in the first case + // immediately. + if !atLeastOneInListsMatch(assignedOnLineAbove, assignedFirstInBlock) { + p.addError(t.Pos(), "type switch statements should only be cuddled with variables switched") + } + } + 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("stmt type not implemented", 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(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(stmt, nextStatement) + p.parseBlockStatements(statementBodyContent) + default: + p.addWarning( + "body statement type not implemented ", + 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.IncDecStmt, + *ast.RangeStmt: + // Nothing to add to LHS + 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("UNKNOWN LHS", 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? + // Needed for defer as of now + return p.findRhs(t.X) + 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) + default: + if x, ok := maybeX(t); ok { + return p.findRhs(x) + } + + p.addWarning("UNKNOWN RHS", t.Pos(), t) + } + + return rhs +} + +// 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(stmt, nextStatement ast.Node) { + var ( + allowedLinesBeforeFirstStatement = 1 + commentMap = ast.NewCommentMap(p.fileSet, stmt, p.file.Comments) + blockStatements []ast.Stmt + blockStartLine int + blockEndLine int + ) + + // 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 + blockStartLine = p.fileSet.Position(t.Lbrace).Line + blockEndLine = p.fileSet.Position(t.Rbrace).Line + case *ast.CaseClause: + blockStatements = t.Body + blockStartLine = p.fileSet.Position(t.Colon).Line + case *ast.CommClause: + blockStatements = t.Body + blockStartLine = p.fileSet.Position(t.Colon).Line + default: + p.addWarning("whitespace node type not implemented ", stmt.Pos(), stmt) + + return + } + + // Ignore empty blocks even if they have newlines or just comments. + if len(blockStatements) < 1 { + 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. + if c, ok := commentMap[firstStatement]; ok { + for _, commentGroup := range c { + var ( + start = p.fileSet.Position(commentGroup.Pos()).Line + ) + + // If the comment group is on the same lince as the block start + // (LBrace) we should not consider it. + if start == 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 + } + + allowedLinesBeforeFirstStatement += len(commentGroup.List) + } + } + + if p.fileSet.Position(firstStatement.Pos()).Line != blockStartLine+allowedLinesBeforeFirstStatement { + p.addErrorOffset( + firstStatement.Pos(), + -1, + "block should not start with a whitespace", + ) + } + + // If the blockEndLine is 0 we're a case clause. 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. + if blockEndLine == 0 { + if nextStatement == nil { + return + } + + switch n := nextStatement.(type) { + case *ast.CaseClause: + blockEndLine = p.fileSet.Position(n.Colon).Line + case *ast.CommClause: + blockEndLine = p.fileSet.Position(n.Colon).Line + default: + // We're not at the end of the case? + return + } + } + + if p.fileSet.Position(lastStatement.End()).Line != blockEndLine-1 { + p.addErrorOffset( + lastStatement.End(), + 1, + "block should not end with a whitespace (or comment)", + ) + } +} + +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) { + p.addErrorOffset(pos, 0, reason) +} + +// Add an error for the file for the current token.Pos with the given offset and +// reason. The offset will be added to the token.Pos line. +func (p *processor) addErrorOffset(pos token.Pos, offset int, reason string) { + position := p.fileSet.Position(pos) + + p.result = append(p.result, result{ + FileName: position.Filename, + LineNumber: position.Line + offset, + 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), + ) +} diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v2/decode.go index e4e56e28..53108765 100644 --- a/vendor/gopkg.in/yaml.v2/decode.go +++ b/vendor/gopkg.in/yaml.v2/decode.go @@ -229,6 +229,10 @@ type decoder struct { mapType reflect.Type terrors []string strict bool + + decodeCount int + aliasCount int + aliasDepth int } var ( @@ -314,7 +318,39 @@ func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unm return out, false, false } +const ( + // 400,000 decode operations is ~500kb of dense object declarations, or ~5kb of dense object declarations with 10000% alias expansion + alias_ratio_range_low = 400000 + // 4,000,000 decode operations is ~5MB of dense object declarations, or ~4.5MB of dense object declarations with 10% alias expansion + alias_ratio_range_high = 4000000 + // alias_ratio_range is the range over which we scale allowed alias ratios + alias_ratio_range = float64(alias_ratio_range_high - alias_ratio_range_low) +) + +func allowedAliasRatio(decodeCount int) float64 { + switch { + case decodeCount <= alias_ratio_range_low: + // allow 99% to come from alias expansion for small-to-medium documents + return 0.99 + case decodeCount >= alias_ratio_range_high: + // allow 10% to come from alias expansion for very large documents + return 0.10 + default: + // scale smoothly from 99% down to 10% over the range. + // this maps to 396,000 - 400,000 allowed alias-driven decodes over the range. + // 400,000 decode operations is ~100MB of allocations in worst-case scenarios (single-item maps). + return 0.99 - 0.89*(float64(decodeCount-alias_ratio_range_low)/alias_ratio_range) + } +} + func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) { + d.decodeCount++ + if d.aliasDepth > 0 { + d.aliasCount++ + } + if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) { + failf("document contains excessive aliasing") + } switch n.kind { case documentNode: return d.document(n, out) @@ -353,7 +389,9 @@ func (d *decoder) alias(n *node, out reflect.Value) (good bool) { failf("anchor '%s' value contains itself", n.value) } d.aliases[n] = true + d.aliasDepth++ good = d.unmarshal(n.alias, out) + d.aliasDepth-- delete(d.aliases, n) return good } diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v2/resolve.go index 6c151db6..4120e0c9 100644 --- a/vendor/gopkg.in/yaml.v2/resolve.go +++ b/vendor/gopkg.in/yaml.v2/resolve.go @@ -81,7 +81,7 @@ func resolvableTag(tag string) bool { return false } -var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`) +var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`) func resolve(tag string, in string) (rtag string, out interface{}) { if !resolvableTag(tag) { diff --git a/vendor/gopkg.in/yaml.v2/scannerc.go b/vendor/gopkg.in/yaml.v2/scannerc.go index 077fd1dd..570b8ecd 100644 --- a/vendor/gopkg.in/yaml.v2/scannerc.go +++ b/vendor/gopkg.in/yaml.v2/scannerc.go @@ -906,6 +906,9 @@ func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool { return true } +// max_flow_level limits the flow_level +const max_flow_level = 10000 + // Increase the flow level and resize the simple key list if needed. func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { // Reset the simple key on the next level. @@ -913,6 +916,11 @@ func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { // Increase the flow level. parser.flow_level++ + if parser.flow_level > max_flow_level { + return yaml_parser_set_scanner_error(parser, + "while increasing flow level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_flow_level)) + } return true } @@ -925,6 +933,9 @@ func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { return true } +// max_indents limits the indents stack size +const max_indents = 10000 + // Push the current indentation level to the stack and set the new level // the current column is greater than the indentation level. In this case, // append or insert the specified token into the token queue. @@ -939,6 +950,11 @@ func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml // indentation level. parser.indents = append(parser.indents, parser.indent) parser.indent = column + if len(parser.indents) > max_indents { + return yaml_parser_set_scanner_error(parser, + "while increasing indent level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_indents)) + } // Create a token and insert it into the queue. token := yaml_token_t{ diff --git a/vendor/modules.txt b/vendor/modules.txt index 8e44d6c1..0be64c50 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -4,6 +4,8 @@ github.com/BurntSushi/toml github.com/OpenPeeDeeP/depguard # github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 github.com/StackExchange/wmi +# github.com/bombsimon/wsl v1.0.1 +github.com/bombsimon/wsl # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew # github.com/fatih/color v1.7.0 @@ -238,7 +240,7 @@ golang.org/x/tools/internal/gopathwalk golang.org/x/tools/internal/imports golang.org/x/tools/internal/module golang.org/x/tools/internal/semver -# gopkg.in/yaml.v2 v2.2.2 +# gopkg.in/yaml.v2 v2.2.4 gopkg.in/yaml.v2 # honnef.co/go/tools v0.0.1-2019.2.3 honnef.co/go/tools/arg