diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d8d91e4..89bbe28 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -2,6 +2,8 @@ name: CI on: push: + branches: + - main pull_request: jobs: @@ -9,53 +11,51 @@ jobs: name: golangci-lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: - go-version: '1.20' - - uses: actions/checkout@v3 - - uses: golangci/golangci-lint-action@v3 + go-version: stable + - uses: golangci/golangci-lint-action@v8 + with: + version: v2.1.6 tests: - # run after golangci-lint action to not produce duplicated errors name: tests needs: golangci-lint strategy: matrix: go: - - '1.20' + - oldstable + - stable os: - ubuntu-latest runs-on: ${{ matrix.os }} steps: - - name: Install Go - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.go }} - - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - name: Setup cache - uses: actions/cache@v3 + - name: Install Go + uses: actions/setup-go@v5 with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + go-version: ${{ matrix.go }} - name: Test code run: go test -race -v ./... + - name: Run + run: go run ./cmd/wsl/ ./... + coverage: runs-on: ubuntu-latest steps: - - name: Install Go + - name: Checkout code if: success() - uses: actions/setup-go@v3 - with: - go-version: '1.20' + uses: actions/checkout@v4 - - name: Checkout code - uses: actions/checkout@v3 + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: stable - name: Calc coverage run: | diff --git a/.golangci.yml b/.golangci.yml index 5430120..8386303 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,81 +1,82 @@ --- -run: - deadline: 1m - issues-exit-code: 1 - tests: true - skip-dirs: - - vendor$ +version: "2" output: - format: colored-line-number - print-issued-lines: false - -linters-settings: - gocognit: - min-complexity: 10 - - depguard: - list-type: blacklist - include-go-root: false - packages: - - github.com/davecgh/go-spew/spew - - misspell: - locale: US - - gocritic: - # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` - # to see all tags and checks. Empty list by default. See - # https://github.com/go-critic/go-critic#usage -> section "Tags". - enabled-tags: - - diagnostic - - experimental - - opinionated - - performance - - style + formats: + text: + path: stdout + print-issued-lines: false linters: - enable-all: true + default: all disable: - cyclop - - deadcode - depguard - dupl - dupword - - exhaustivestruct + - err113 - exhaustruct - forbidigo + - funcorder - funlen - - gci - gocognit - gocyclo + - godot - godox - - golint - - gomnd - - ifshort - - interfacer - lll - maintidx - - maligned + - mnd - nakedret - nestif - nlreturn - - nosnakecase - paralleltest - prealloc - rowserrcheck - - scopelint - - structcheck + - tagliatelle - testpackage - - varcheck + - tparallel - varnamelen - wastedassign - fast: false + settings: + depguard: + rules: + main: + deny: + - pkg: github.com/davecgh/go-spew/spew + desc: not allowed + gocognit: + min-complexity: 10 + gocritic: + # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` + # to see all tags and checks. Empty list by default. See + # https://github.com/go-critic/go-critic#usage -> section "Tags". + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + misspell: + locale: US + + exclusions: + presets: + - comments + - common-false-positives + - std-error-handling issues: - exclude-use-default: true max-issues-per-linter: 0 max-same-issues: 0 - +formatters: + enable: + - gofmt + - gofumpt + - goimports + settings: + gofmt: + rewrite-rules: + - pattern: "interface{}" + replacement: "any" # vim: set sw=2 ts=2 et: diff --git a/CHECKS.md b/CHECKS.md new file mode 100644 index 0000000..a26f209 --- /dev/null +++ b/CHECKS.md @@ -0,0 +1,1287 @@ +# Checks + +This document describes all the checks done by `wsl` with examples of what's not +allowed and what's allowed. + +## `assign` + +Assign (`foo := bar`) or re-assignments (`foo = bar`) should only be cuddled +with other assignments or increment/decrement. + +
| Bad | Good |
|---|---|
| + +```go +if true { + fmt.Println("hello") +} +a := 1 // 1 + +defer func() { + fmt.Println("hello") +}() +a := 1 // 2 +``` + + | + +```go +if true { + fmt.Println("hello") +} + +a := 1 + +defer func() { + fmt.Println("hello") +}() + +a := 1 + +a := 1 +b := 2 +c := 3 +``` + + |
| + +1 Not an assign statement above + +2 Not an assign statement above + + | + + |
| Bad | Good |
|---|---|
| + +```go +for { + a, err : = SomeFn() + if err != nil { + return err + } + + fmt.Println(a) + break // 1 +} +``` + + | + +```go +for { + a, err : = SomeFn() + if err != nil { + return err + } + + fmt.Println(a) + + break +} + +for { + fmt.Println("hello") + break +} +``` + + |
| + +1 Block is more than 2 lines so should be a blank line above + + | + + |
| Bad | Good |
|---|---|
| + +```go +var a string +var b int // 1 + +const a = 1 +const b = 2 // 2 + +a := 1 +var b string // 3 + +fmt.Println("hello") +var a string // 4 +``` + + | + +```go +var ( + a string + b int +) + +const ( + a = 1 + b = 2 +) + +a := 1 + +var b string + +fmt.Println("hello") + +var a string +``` + + |
| + +1 Multiple declarations should be grouped to one + +2 Multiple declarations should be grouped to one + +3 Declaration should always have a whitespace above + +4 Declaration should always have a whitespace above + + | + + |
| Bad | Good |
|---|---|
| + +```go +val, closeFn := SomeFn() +val2 := fmt.Sprintf("v-%s", val) +fmt.Println(val) +defer closeFn() // 1 + +defer fn1() +a := 1 +defer fn3() // 2 + +f, err := os.Open("/path/to/f.txt") +if err != nil { + return err +} + +lines := ReadFile(f) +trimLines(lines) +defer f.Close() // 3 +``` + + | + +```go +val, closeFn := SomeFn() +defer closeFn() + +defer fn1() +defer fn2() +defer fn3() + +f, err := os.Open("/path/to/f.txt") +if err != nil { + return err +} +defer f.Close() + +m.Lock() +defer m.Unlock() +``` + + |
| + +1 More than a single statement between `defer` and `closeFn` + +2 `a` is not used in expression + +3 More than a single statement between `defer` and `f.Close` + + | + + |
| Bad | Good |
|---|---|
| + +```go +a := 1 +b := 2 +fmt.Println("not b") // 1 +``` + + | + +```go +a := 1 +b := 2 + +fmt.Println("not b") + +a := 1 +fmt.Println(a) +``` + + |
| + +1 `b` is not used in expression + + | + + |
| Bad | Good |
|---|---|
| + +```go +i := 0 +for j := 0; j < 3; j++ { // 1 + fmt.Println(j) +} + +a := 0 +i := 3 +for j := 0; j < i; j++ { // 2 + fmt.Println(j) +} + +x := 1 +for { // 3 + fmt.Println("hello") + break +} +``` + + | + +```go +i := 0 +for j := 0; j < i; j++ { + fmt.Println(j) +} + +a := 0 + +i := 3 +for j := 0; j < i; j++ { + fmt.Println(j) +} + +// Allowed with `allow-first-in-block` +x := 1 +for { + x++ + break +} + +// Allowed with `allow-whole-block` +x := 1 +for { + fmt.Println("hello") + + if shouldIncrement() { + x++ + } +} +``` + + |
| + +1 `i` is not used in expression + +2 More than one variable above statement + +3 No variable in expression + + | + + |
| Bad | Good |
|---|---|
| + +```go +someFunc := func() {} +go anotherFunc() // 1 + +x := 1 +go func () // 2 + fmt.Println(y) +}() + +someArg := 1 +go Fn(notArg) // 3 +``` + + | + +```go +someFunc := func() {} +go someFunc() + +x := 1 +go func (s string) { + fmt.Println(s) +}(x) + +someArg := 1 +go Fn(someArg) +``` + + |
| + +1 `someFunc` is not used in expression + +2 `x` is not used in expression + +3 `someArg` is not used in expression + + | + + |
| Bad | Good |
|---|---|
| + +```go +x := 1 +if y > 1 { // 1 + fmt.Println("y > 1") +} + +a := 1 +b := 2 +if b > 1 { // 2 + fmt.Println("a > 1") +} + +a := 1 +b := 2 +if a > 1 { // 3 + fmt.Println("a > 1") +} + +a := 1 +b := 2 +if notEvenAOrB() { // 4 + fmt.Println("not a or b") +} + +a := 1 +x, err := SomeFn() // 5 +if err != nil { + return err +} +``` + + | + +```go +x := 1 + +if y > 1 { + fmt.Println("y > 1") +} + +a := 1 + +b := 2 +if b > 1 { + fmt.Println("a > 1") +} + +b := 2 + +a := 1 +if a > 1 { + fmt.Println("a > b") +} + +a := 1 +b := 2 + +if notEvenAOrB() { + fmt.Println("not a or b") +} + +a := 1 + +x, err := SomeFn() +if err != nil { + return err +} + +// Allowed with `allow-first-in-block` +x := 1 +if xUsedFirstInBlock() { + x = 2 +} + +// Allowed with `allow-whole-block` +x := 1 +if xUsedLaterInBlock() { + fmt.Println("will use x later") + + if orEvenNestedWouldWork() { + x = 3 + } +} +``` + + |
| + +1 `x` is not used in expression + +2 More than one variable above statement + +3 `b` is not used in expression and too many statements + +4 No variable in expression + +5 More than one variable above statement + + | + + |
| Bad | Good |
|---|---|
| + +```go +i := 1 + +if true { + fmt.Println("hello") +} +i++ // 1 + +defer func() { + fmt.Println("hello") +}() +i++ // 2 +``` + + | + +```go +i := 1 +i++ + +i-- +j := i +j++ +``` + + |
| + +1 Not an assign or inc/dec statement above + +2 Not an assign or inc/dec statement above + + | + + |
| Bad | Good |
|---|---|
| + +```go +L1: + if true { + _ = 1 + } +L2: // 1 + if true { + _ = 1 + } +``` + + | + +```go +L1: + if true { + _ = 1 + } + +L2: + if true { + _ = 1 + } +``` + + |
| + +1 Labels should always have a whitespabe above + + | + + |
| Bad | Good |
|---|---|
| + +```go +someRange := []int{1, 2, 3} +for _, i := range thisIsNotSomeRange { // 1 + fmt.Println(i) +} + +x := 1 +for i := range make([]int, 3) { // 2 + fmt.Println("hello") + break +} + +s1 := []int{1, 2, 3} +s2 := []int{3, 2, 1} +for _, v := range s2 { // 3 + fmt.Println(v) +} +``` + + | + +```go +someRange := []int{1, 2, 3} + +for _, i := range thisIsNotSomeRange { + fmt.Println(i) +} + +someRange := []int{1, 2, 3} +for _, i := range someRange { + fmt.Println(i) +} + +notARange := 1 +for i := range returnsRange(notARange) { + fmt.Println(i) +} + +s1 := []int{1, 2, 3} + +s2 := []int{3, 2, 1} +for _, v := range s2 { + fmt.Println(v) +} +``` + + |
| + +1 `someRange` is not used in expression + +2 `x` is not used in expression + +3 More than one variable above statement + + | + + |
| Bad | Good |
|---|---|
| + +```go +func Fn() int { + x, err := someFn() + if err != nil { + panic(err) + } + + fmt.Println(x) + return // 1 +} +``` + + | + +```go +func Fn() int { + x, err := someFn() + if err != nil { + panic(err) + } + + fmt.Println(x) + + return +} +``` + + |
| + +1 Block is more than 2 lines so should be a blank line above + + | + + |
| Bad | Good |
|---|---|
| + +```go +x := 0 +select { // 1 +case <-time.After(time.Second): + // ... +case <-stop: + // ... +} +``` + + | + +```go +x := 0 + +select { +case <-time.After(time.Second): + // ... +case <-stop: + // ... +} + +// Allowed with `allow-whole-block` +x := 1 +select { +case <-time.After(time.Second): + // ... +case <-stop: + Fn(x) +} +``` + + |
| + +1 `x` is not used in expression + + | + + |
| Bad | Good |
|---|---|
| + +```go +a := 1 +ch <- 1 // 1 + +b := 2 +<-ch // 2 +``` + + | + +```go +a := 1 +ch <- a + +b := 1 + +<-ch +``` + + |
| + +1 `a` is not used in expression + +2 `b` is not used in expression + + | + + |
| Bad | Good |
|---|---|
| + +```go +x := 0 +switch y { // 1 +case 1: + // ... +case 2: + // ... +} + + +x := 0 +y := 1 +switch y { // 2 +case 1: + // ... +case 2: + // ... +} +``` + + | + +```go +x := 0 + +switch y { +case 1: + // ... +case 2: + // ... +} + + +x := 0 + +y := 1 +switch y { +case 1: + // ... +case 2: + // ... +} + +// Allowed with `allow-whole-block` +x := 1 +switch y { +case 1: + // ... +case 2: + fmt.Println(x) +} +``` + + |
| + +1 `x` is not used in expression + +2 More than one variable above statement + + | + + |
| Bad | Good |
|---|---|
| + +```go +x := someType() +switch y.(type) { // 1 +case int32: + // ... +case int64: + // ... +} + + +x := 0 +y := someType() +switch y.(type) { +case int32: + // ... +case int64: + // ... +} +``` + + | + +```go +x := someType() + +switch y.(type) { +case int32: + // ... +case int64: + // ... +} + + +x := 0 + +y := someType() +switch y.(type) { +case int32: + // ... +case int64: + // ... +} + +// Allowed with `allow-whole-block` +x := 1 +switch y.(type) { +case int32: + // ... +case int64: + fmt.Println(x) +} +``` + + |
| + +1 `x` is not used in expression + + | + + |
| Bad | Good |
|---|---|
| + +```go +s := []string{} + +a := 1 +s = append(s, 2) // 1 +b := 3 +s = append(s, a) // 2 +``` + + | + +```go +s := []string{} + +a := 1 +s = append(s, a) + +b := 3 + +s = append(s, 2) +``` + + |
| + +1 `a` is not used in append + +2 `b` is not used in append + + | + + |
| Bad | Good |
|---|---|
| + +```go +a := 1 +b = 2 // 1 +c := 3 // 2 +d = 4 // 3 +``` + + | + +```go +a := 1 +c := 3 + +b = 2 +d = 4 +``` + + |
| + +1 `a` is not a re-assignment + +2 `b` is not a new assignment + +3 `c` is not a re-assignment + + | + + |
| Bad | Good |
|---|---|
| + +```go +t1.Fn1() +x := t1.Fn2() // 1 +t1.Fn3() +``` + + | + +```go +t1.Fn1() + +x := t1.Fn2() +t1.Fn3() +``` + + |
| + +1 Line above is not an assignment + + | + + |
| Bad | Good |
|---|---|
| + +```go +_, err := SomeFn() + +if err != nil { // 1 + return fmt.Errorf("failed to fn: %w", err) +} +``` + + | + +```go +_, err := SomeFn() +if err != nil { + return fmt.Errorf("failed to fn: %w", err) +} +``` + + |
| + +1 Whitespace between error assignment and error checking + + | + + |
| Bad | Good |
|---|---|
| + +```go +if true { + + fmt.Println("hello") +} +``` + + | + +```go +if true { + fmt.Println("hello") +} +``` + + |
| Bad | Good |
|---|---|
| + +```go +if true { + fmt.Println("hello") + +} +``` + + | + +```go +if true { + fmt.Println("hello") +} +``` + + |