Skip to content

Commit

Permalink
cmd/gosimple: move documentation to staticcheck.io
Browse files Browse the repository at this point in the history
  • Loading branch information
dominikh committed Aug 5, 2017
1 parent feb1e82 commit 74be44e
Show file tree
Hide file tree
Showing 29 changed files with 403 additions and 115 deletions.
118 changes: 3 additions & 115 deletions cmd/gosimple/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,119 +9,7 @@ Gosimple requires Go 1.6 or later.

go get honnef.co/go/tools/cmd/gosimple

## Usage
## Documentation

Invoke `gosimple` with one or more filenames, a directory, or a package named
by its import path. Gosimple uses the same
[import path syntax](https://golang.org/cmd/go/#hdr-Import_path_syntax) as
the `go` command and therefore
also supports relative import paths like `./...`. Additionally the `...`
wildcard can be used as suffix on relative and absolute file paths to recurse
into them.

The output of this tool is a list of suggestions in Vim quickfix format,
which is accepted by lots of different editors.

## Purpose

Gosimple differs from golint in that gosimple focuses on simplifying
code, while golint flags common style issues. Furthermore, gosimple
always targets the latest Go version. If a new Go release adds a
simpler way of doing something, gosimple will suggest that way.

## Checks

Gosimple makes the following recommendations for avoiding unsimple
constructs:

| Check | Description | Suggestion |
|-------|-----------------------------------------------------------------------------|--------------------------------------------------------------------------|
| S1000 | `select{}` with a single case | Use a plain channel send or receive |
| S1001 | A loop copying elements of `s2` to `s1` | `copy(s1, s2)` |
| S1002 | `if b == true` | `if b` |
| S1003 | `strings.Index*(x, y) != -1` | `strings.Contains(x, y)` |
| S1004 | `bytes.Compare(x, y) == 0` | `bytes.Equal(x, y)` |
| S1005 | Unnecessary use of blank identifier | |
| S1006 | `for true {...}` | `for {...}` |
| S1007 | Using double quotes and escaping for regular expressions | Use raw strings |
| S1008 | `if <expr> { return <bool> }; return <bool>` | `return <expr>` |
| S1009 | Checking a slice against nil and also checking its length against zero | Nil slices are defined to have length zero, the nil check is redundant |
| S1010 | `s[a:len(s)]` | `s[a:]` |
| S1011 | A loop appending each element of `s2` to `s1` | `append(s1, s2...)` |
| S1012 | `time.Now().Sub(x)` | `time.Since(x)` |
| S1013 | `if err != nil { return err }; return nil` | `return err` |
| S1016 | Converting two struct types by manually copying each field | A type conversion: `T(v)` |
| S1017 | `if strings.HasPrefix` + string slicing | Call `strings.TrimPrefix` unconditionally |
| S1018 | A loop sliding elements in a slice to the beginning | `copy(s[:n], s[offset:])` |
| S1019 | `make(T, 0)` or `make(T, x, x)` | `make(T)` or `make(T, x)` |
| S1020 | `if _, ok := i.(T); ok && i != nil` | `if _, ok := i.(T); ok` |
| S1021 | `var x uint; x = 1` | `var x uint = 1` |
| S1023 | redundant control flow | |
| S1024 | `t.Sub(time.Now())` | `time.Until(t)` |
| S1025 | `fmt.Sprintf("%s", x)` where `x` is already a string | `x` |
| | `fmt.Sprintf("%s", x)` where `x`'s underlying type is a string | `string(x)` |
| | `fmt.Sprintf("%s", x)` where `x` has a String method | `x.String()` |
| S1026 | Copies of strings, like `string([]byte(x))` or `"" + x` | `x` |
| S1028 | `errors.New(fmt.Sprintf(...))` | `fmt.Errorf(...)` |
| S1029 | `for _, r := range []rune(s)` | `for _, r := range s` |
| S1030 | `string(buf.Bytes())` or `[]byte(buf.String())` | Use the appropriate method of `bytes.Buffer` instead |

## gofmt -r

Some of these rules can be automatically applied via `gofmt -r`:

```
strings.IndexRune(a, b) > -1 -> strings.ContainsRune(a, b)
strings.IndexRune(a, b) >= 0 -> strings.ContainsRune(a, b)
strings.IndexRune(a, b) != -1 -> strings.ContainsRune(a, b)
strings.IndexRune(a, b) == -1 -> !strings.ContainsRune(a, b)
strings.IndexRune(a, b) < 0 -> !strings.ContainsRune(a, b)
strings.IndexAny(a, b) > -1 -> strings.ContainsAny(a, b)
strings.IndexAny(a, b) >= 0 -> strings.ContainsAny(a, b)
strings.IndexAny(a, b) != -1 -> strings.ContainsAny(a, b)
strings.IndexAny(a, b) == -1 -> !strings.ContainsAny(a, b)
strings.IndexAny(a, b) < 0 -> !strings.ContainsAny(a, b)
strings.Index(a, b) > -1 -> strings.Contains(a, b)
strings.Index(a, b) >= 0 -> strings.Contains(a, b)
strings.Index(a, b) != -1 -> strings.Contains(a, b)
strings.Index(a, b) == -1 -> !strings.Contains(a, b)
strings.Index(a, b) < 0 -> !strings.Contains(a, b)
bytes.Index(a, b) > -1 -> bytes.Contains(a, b)
bytes.Index(a, b) >= 0 -> bytes.Contains(a, b)
bytes.Index(a, b) != -1 -> bytes.Contains(a, b)
bytes.Index(a, b) == -1 -> !bytes.Contains(a, b)
bytes.Index(a, b) < 0 -> !bytes.Contains(a, b)
bytes.Compare(a, b) == 0 -> bytes.Equal(a, b)
bytes.Compare(a, b) != 0 -> !bytes.Equal(a, b)
time.Now().Sub(a) -> time.Since(a)
a.Sub(time.Now()) -> time.Until(a)
```

## Ignoring checks

gosimple allows disabling some or all checks for certain files. The
`-ignore` flag takes a whitespace-separated list of
`glob:check1,check2,...` pairs. `glob` is a glob pattern matching
files in packages, and `check1,check2,...` are checks named by their
IDs.

For example, to ignore uses of strconv.FormatInt in all test files in the
`os/exec` package, you would write `-ignore
"os/exec/*_test.go:S1015"`

Additionally, the check IDs support globbing, too. Using a pattern
such as `os/exec/*.gen.go:*` would disable all checks in all
auto-generated files in the os/exec package.

Any whitespace can be used to separate rules, including newlines. This
allows for a setup like the following:

```
$ cat stdlib.ignore
sync/*_test.go:S1000
testing/benchmark.go:S1016
runtime/string_test.go:S1005
$ gosimple -ignore "$(cat stdlib.ignore)" std
```
Detailed documentation can be found on
[staticcheck.io](https://staticcheck.io/docs/gosimple).
1 change: 1 addition & 0 deletions cmd/gosimple/docs/categories/S1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Code simplifications
20 changes: 20 additions & 0 deletions cmd/gosimple/docs/checks/S1000
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Use plain channel send or receive

`select` with a single case can be replaced with a simple send or
receive.

**Before:**

```
select {
case x := <-ch:
fmt.Println(x)
}
```

**After:**

```
x := <-ch
fmt.Println(x)
```
17 changes: 17 additions & 0 deletions cmd/gosimple/docs/checks/S1001
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Replace with `copy()`

Use `copy()` for copying elements from one slice to another.

**Before:**

```
for i, x := range src {
dst[i] = x
}
```

**After:**

```
copy(dst, src)
```
13 changes: 13 additions & 0 deletions cmd/gosimple/docs/checks/S1002
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Omit comparison with boolean constant

**Before:**

```
if x == true {}
```

**After:**

```
if x {}
```
13 changes: 13 additions & 0 deletions cmd/gosimple/docs/checks/S1003
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Replace with `strings.Contains`

**Before:**

```
if strings.Index(x, y) != -1 {}
```

**After:**

```
if strings.Contains(x, y) {}
```
13 changes: 13 additions & 0 deletions cmd/gosimple/docs/checks/S1004
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Replace with `bytes.Equal`

**Before:**

```
if bytes.Compare(x, y) == 0 {}
```

**After:**

```
if bytes.Equal(x, y) {}
```
19 changes: 19 additions & 0 deletions cmd/gosimple/docs/checks/S1005
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Drop unnecessary use of the blank identifier

In many cases, assigning to the blank identifier is unnecessary.

**Before:**

```
for _ = range s {}
x, _ = someMap[key]
_ = <-ch
```

**After:**

```
for range s{}
x = someMap[key]
<-ch
```
3 changes: 3 additions & 0 deletions cmd/gosimple/docs/checks/S1006
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Replace with `for { ... }`

For infinite loops, using `for { ... }` is the most idiomatic choice.
20 changes: 20 additions & 0 deletions cmd/gosimple/docs/checks/S1007
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Simplify regular expression by using raw string literal

Raw string literals use `` ` `` instead of `"` and do not support any escape
sequences. This means that the backslash (`\`) can be used freely,
without the need of escaping.

Since regular expressions have their own escape sequences, raw strings
can improve their readability.

**Before:**

```
regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")
```

**After:**

```
regexp.Compile(`\A(\w+) profile: total \d+\n\z`)
```
16 changes: 16 additions & 0 deletions cmd/gosimple/docs/checks/S1008
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Simplify returning boolean expression

**Before:**

```
if <expr> {
return true
}
return false
```

**After:**

```
return <expr>
```
17 changes: 17 additions & 0 deletions cmd/gosimple/docs/checks/S1009
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Omit redundant nil check on slices

The `len` function is defined for all slices, even nil ones, which
have a length of zero. It is not necessary to check if a slice is not
nil before checking that its length is not zero.

**Before:**

```
if x != nil && len(x) != 0 {}
```

**After:**

```
if len(x) != 0 {}
```
4 changes: 4 additions & 0 deletions cmd/gosimple/docs/checks/S1010
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Omit default slice index

When slicing, the second index defaults to the length of the value,
making `s[n:len(s)]` and `s[n:]` equivalent.
15 changes: 15 additions & 0 deletions cmd/gosimple/docs/checks/S1011
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Use a single append to concatenate two slices

**Before:**

```
for _, e := range y {
x = append(x, e)
}
```

**After:**

```
x = append(x, y...)
```
16 changes: 16 additions & 0 deletions cmd/gosimple/docs/checks/S1012
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Replace with `time.Since(x)`

The `time.Since` helper has the same effect as using
`time.Now().Sub(x)` but is easier to read.

**Before:**

```
time.Now().Sub(x)
```

**After:**

```
time.Since(x)
```
21 changes: 21 additions & 0 deletions cmd/gosimple/docs/checks/S1013
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Simplify returning final error

**Before:**

```
if err != nil {
return err
}
return nil
```

**After:**

```
return err
```

**Note:**

This simplification is only valid if `err` is an interface value and
not of a concrete type.
24 changes: 24 additions & 0 deletions cmd/gosimple/docs/checks/S1016
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Use a type conversion

Two struct types with identical fields can be converted between each
other. In older versions of Go, the fields had to have identical
struct tags. Since Go 1.8, however, struct tags are ignored during
conversions. It is thus not necessary to manually copy every field
individually.

**Before:**

```
var x T1
y := T2{
Field1: x.Field1,
Field2: x.Field2,
}
```

**After:**

```
var x T1
y := T2(x)
```
21 changes: 21 additions & 0 deletions cmd/gosimple/docs/checks/S1017
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Replace with `strings.TrimPrefix`

Instead of using `strings.HasPrefix` and manual slicing, use the
`strings.TrimPrefix` function. If the string doesn't start with the
prefix, the original string will be returned. Using
`strings.TrimPrefix` reduces complexity, and avoids common bugs, such
as off-by-one mistakes.

**Before:**

```
if strings.HasPrefix(str, prefix) {
str = str[len(prefix):]
}
```

**After:**

```
str = strings.TrimPrefix(str, prefix)
```
Loading

0 comments on commit 74be44e

Please sign in to comment.