1
0
mirror of https://github.com/mgechev/revive.git synced 2024-11-28 08:49:11 +02:00
Conflicts:
	config.go
This commit is contained in:
chavacava 2018-11-12 15:20:30 +01:00
commit 4bb1b4d361
125 changed files with 4422 additions and 328 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
golinter
revive
vendor
*.swp

View File

@ -1,6 +1,17 @@
language: go
go: "1.10.x"
go: 1.11.x
install: make install
script: make build && make test.all
script:
- make build
- make test
- git clone https://github.com/mgechev/revive.run revive-docs && cd revive-docs
- npm i && npm run build || true
- git config --global user.email "mgechev@gmail.com"
- git config --global user.name "mgechev"
- git add .
- git commit -am 'Travis CI deployment'
- git push --force --quiet "https://${GH_TOKEN}@${GH_REF}"
env:
global:
- GH_REF: github.com/mgechev/revive.run.git
- secure: Bp2/fJOVEor5aj3rNwA3+4/wecCmX2mVQ2pQt1AJ1n+pT6MjKYywswTDT6kzK/Cu1bPcfEGh3a7XKieAhIWVKvchoyYVV8J80F76HGyqgSBuzFXvV0c32zFn2LtxQxvmtCNynjmGAV57dHtsxGmHxkX9u8JIJ4J06E2Eq9nuuflTCf5o5gHtaE7P7hQT2WL/JRJVABHUMa0XzsMuUdRNO0OBXGMm+SqiWEcZetf2Vq3tfo2LL4ula99oTKKspg0iRKiauCZZaRxyZG/V3QiR0Rl9nhTVnb6hx6/RFrxru63Pm1FUaK1gIqEq9EUMpZRTddGW77UPp9GSB81/GaUm+e0GNFjUAL2e59t72wMxCQEOT+835hVbeCjgdksg0IDbn7sR/S+rYbiCyxTuCA/4YNlDoEajl9RMxK4culsq35LnibE1x7L4Q/5blD7HwVSMhA33HSDCC5MINwTdWwsdHYiAvFo0RCi5B0GngMzE6/pJxvYhWV3YhKWrgSmhafV1QO3Qu9dCn6P+7KsEVDbUeA8Yxnugd60kQNh2vG7bdTYKaZ6GhfU12zAM15xd2SSrKl6szSAo64CYOTznNFMBMskpm05SubTW1w+xDQy8vGjIHqb6zntiqUhFhTDd326iRYfrQuhAK53XbU1NUFFOyZa8kCTlSsPWDoSOX68oH9Q=

View File

@ -11,7 +11,7 @@ go get -u github.com/mgechev/revive
cd $GOPATH/src/github.com/mgechev/revive
```
After that install the dependencies using dep:
After that install the dependencies using go modules:
```bash
make install

73
Gopkg.lock generated
View File

@ -1,73 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/BurntSushi/toml"
packages = ["."]
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
version = "v0.3.0"
[[projects]]
name = "github.com/fatih/color"
packages = ["."]
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
version = "v1.7.0"
[[projects]]
name = "github.com/mattn/go-colorable"
packages = ["."]
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
name = "github.com/mattn/go-runewidth"
packages = ["."]
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
version = "v0.0.2"
[[projects]]
branch = "master"
name = "github.com/mgechev/dots"
packages = ["."]
revision = "3d1c0cc50642eae6291b43fdd37c7d7acfff7975"
[[projects]]
branch = "master"
name = "github.com/olekukonko/tablewriter"
packages = ["."]
revision = "d4647c9c7a84d847478d890b816b7d8b62b0b279"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "c11f84a56e43e20a78cee75a7c034031ecf57d1f"
[[projects]]
branch = "master"
name = "golang.org/x/tools"
packages = [
"go/gcexportdata",
"go/internal/gcimporter",
"go/types/typeutil"
]
revision = "a5b4c53f6e8bdcafa95a94671bf2d1203365858b"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "55ca94323f5133e6922ce74ad093146191929c198c0aa732c84e7ee6070af701"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,41 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/BurntSushi/toml"
version = "0.3.0"
[[constraint]]
name = "github.com/fatih/color"
version = "1.5.0"
[[constraint]]
branch = "master"
name = "github.com/olekukonko/tablewriter"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"
[[constraint]]
branch = "master"
name = "golang.org/x/tools"

View File

@ -1,12 +1,13 @@
deps.devtools:
@go get github.com/golang/dep/cmd/dep
.PHONY: test
install: deps.devtools
@dep ensure -v
export GO111MODULE=on
install:
@go mod vendor
build:
@go build
test.all:
test:
@go test -v ./test/...

12
PULL_REQUEST_TEMPLATE.md Normal file
View File

@ -0,0 +1,12 @@
<!-- ### IMPORTANT ### -->
<!-- Please do not create a Pull Request without creating an issue first.** -->
<!-- If you're fixing a typo or improving the documentation, you may not have to open an issue. -->
<!-- ### CHECKLIST ### -->
<!-- Please, describe in details what's your motivation for this PR -->
<!-- Did you add tests? -->
<!-- Does your code follows the coding style of the rest of the repository? -->
<!-- Does the Travis build passes? -->
<!-- ### FOOTER (OPTIONAL) ### -->
<!-- If you're closing an issue add "Closes #XXXX" in your comment. This way, the PR will be linked to the issue automatically. -->

215
README.md
View File

@ -30,29 +30,32 @@ Here's how `revive` is different from `golint`:
<!-- TOC -->
- [revive](#revive)
- [Usage](#usage)
- [Text Editors](#text-editors)
- [Installation](#installation)
- [Command Line Flags](#command-line-flags)
- [Sample Invocations](#sample-invocations)
- [Comment Annotations](#comment-annotations)
- [Configuration](#configuration)
- [Default Configuration](#default-configuration)
- [Recommended Configuration](#recommended-configuration)
- [Available Rules](#available-rules)
- [Available Formatters](#available-formatters)
- [Friendly](#friendly)
- [Stylish](#stylish)
- [Default](#default)
- [Extensibility](#extensibility)
- [Custom Rule](#custom-rule)
- [Example](#example)
- [Custom Formatter](#custom-formatter)
- [Speed Comparison](#speed-comparison)
- [golint](#golint)
- [revive](#revive-1)
- [Contributors](#contributors)
- [License](#license)
- [Usage](#usage)
- [Text Editors](#text-editors)
- [Installation](#installation)
- [Command Line Flags](#command-line-flags)
- [Sample Invocations](#sample-invocations)
- [Comment Annotations](#comment-annotations)
- [Configuration](#configuration)
- [Default Configuration](#default-configuration)
- [Custom Configuration](#custom-configuration)
- [Recommended Configuration](#recommended-configuration)
- [Available Rules](#available-rules)
- [Configurable rules](#configurable-rules)
- [`var-naming`](#var-naming)
- [Available Formatters](#available-formatters)
- [Friendly](#friendly)
- [Stylish](#stylish)
- [Default](#default)
- [Extensibility](#extensibility)
- [Custom Rule](#custom-rule)
- [Example](#example)
- [Custom Formatter](#custom-formatter)
- [Speed Comparison](#speed-comparison)
- [golint](#golint)
- [revive](#revive)
- [Contributors](#contributors)
- [License](#license)
<!-- /TOC -->
@ -63,6 +66,7 @@ Since the default behavior of `revive` is compatible with `golint`, without prov
### Text Editors
- Support for VSCode in [vscode-go](https://github.com/Microsoft/vscode-go/pull/1699).
- Support for Atom via [linter-revive](https://github.com/morphy2k/linter-revive).
- Support for vim via [w0rp/ale](https://github.com/w0rp/ale):
```vim
@ -89,10 +93,13 @@ go get -u github.com/mgechev/revive
- `-config [PATH]` - path to config file in TOML format.
- `-exclude [PATTERN]` - pattern for files/directories/packages to be excluded for linting. You can specify the files you want to exclude for linting either as package name (i.e. `github.com/mgechev/revive`), list them as individual files (i.e. `file.go`), directories (i.e. `./foo/...`), or any combination of the three.
- `-formatter [NAME]` - formatter to be used for the output. The currently available formatters are:
- `default` - will output the failures the same way that `golint` does.
- `json` - outputs the failures in JSON format.
- `ndjson` - outputs the failures as stream in newline delimited JSON (NDJSON) format.
- `friendly` - outputs the failures when found. Shows summary of all the failures.
- `stylish` - formats the failures in a table. Keep in mind that it doesn't stream the output so it might be perceived as slower compared to others.
- `checkstyle` - outputs the failures in XML format compatible with that of Java's [Checkstyle](https://checkstyle.org/).
### Sample Invocations
@ -118,6 +125,8 @@ func Public() {}
The snippet above, will disable `revive` between the `revive:disable` and `revive:enable` comments. If you skip `revive:enable`, the linter will be disabled for the rest of the file.
With `revive:disable-next-line` and `revive:disable-line` you can disable `revive` on a particular code line.
You can do the same on a rule level. In case you want to disable only a particular rule, you can use:
```go
@ -171,7 +180,7 @@ revive -config defaults.toml github.com/mgechev/revive
This will use the configuration file `defaults.toml`, the `default` formatter, and will run linting over the `github.com/mgechev/revive` package.
### Recommended Configuration
### Custom Configuration
```shell
revive -config config.toml -formatter friendly github.com/mgechev/revive
@ -179,41 +188,111 @@ revive -config config.toml -formatter friendly github.com/mgechev/revive
This will use `config.toml`, the `friendly` formatter, and will run linting over the `github.com/mgechev/revive` package.
### Recommended Configuration
The following snippet contains the recommended `revive` configuration that you can use in your project:
```toml
ignoreGeneratedHeader = false
severity = "warning"
confidence = 0.8
errorCode = 0
warningCode = 0
[rule.blank-imports]
[rule.context-as-argument]
[rule.context-keys-type]
[rule.dot-imports]
[rule.error-return]
[rule.error-strings]
[rule.error-naming]
[rule.exported]
[rule.if-return]
[rule.increment-decrement]
[rule.var-naming]
[rule.var-declaration]
[rule.package-comments]
[rule.range]
[rule.receiver-naming]
[rule.time-naming]
[rule.unexported-return]
[rule.indent-error-flow]
[rule.errorf]
[rule.empty-block]
[rule.superfluous-else]
[rule.unused-parameter]
[rule.unreachable-code]
[rule.redefines-builtin-id]
```
## Available Rules
List of all available rules. The rules ported from `golint` are left unchanged and indicated in the `golint` column.
| Name | Config | Description | `golint` | Typed |
| --------------------- | :----: | :--------------------------------------------------------------- | :------: | :---: |
| `context-keys-type` | n/a | Disallows the usage of basic types in `context.WithValue`. | yes | yes |
| `time-naming` | n/a | Conventions around the naming of time variables. | yes | yes |
| `var-declaration` | n/a | Reduces redundancies around variable declaration. | yes | yes |
| `unexported-return` | n/a | Warns when a public return is from unexported type. | yes | yes |
| `errorf` | n/a | Should replace `error.New(fmt.Sprintf())` with `error.Errorf()` | yes | yes |
| `blank-imports` | n/a | Disallows blank imports | yes | no |
| `context-as-argument` | n/a | `context.Context` should be the first argument of a function. | yes | no |
| `dot-imports` | n/a | Forbids `.` imports. | yes | no |
| `error-return` | n/a | The error return parameter should be last. | yes | no |
| `error-strings` | n/a | Conventions around error strings. | yes | no |
| `error-naming` | n/a | Naming of error variables. | yes | no |
| `exported` | n/a | Naming and commenting conventions on exported symbols. | yes | no |
| `if-return` | n/a | Redundant if when returning an error. | yes | no |
| `increment-decrement` | n/a | Use `i++` and `i--` instead of `i += 1` and `i -= 1`. | yes | no |
| `var-naming` | n/a | Naming rules. | yes | no |
| `package-comments` | n/a | Package commenting conventions. | yes | no |
| `range` | n/a | Prevents redundant variables when iterating over a collection. | yes | no |
| `receiver-naming` | n/a | Conventions around the naming of receivers. | yes | no |
| `indent-error-flow` | n/a | Prevents redundant else statements. | yes | no |
| `argument-limit` | int | Specifies the maximum number of arguments a function can receive | no | no |
| `cyclomatic` | int | Sets restriction for maximum Cyclomatic complexity. | no | no |
| `max-public-structs` | int | The maximum number of public structs in a file. | no | no |
| `file-header` | string | Header which each file should have. | no | no |
| `empty-block` | n/a | Warns on empty code blocks | no | no |
| `superfluous-else` | n/a | Prevents redundant else statements (extends `indent-error-flow`) | no | no |
| `confusing-naming` | n/a | Warns on methods with names that differ only by capitalization | no | no |
| `get-return ` | n/a | Warns on getters that do not yield any result | no | no |
| `modifies-param` | n/a | Warns on assignments to function parameters | no | no |
| `deep-exit` | n/a | Looks for program exits in funcs other than `main()` or `init()` | no | no |
| [`context-keys-type`](./RULES_DESCRIPTIONS.md#context-key-types) | n/a | Disallows the usage of basic types in `context.WithValue`. | yes | yes |
| [`time-naming`](./RULES_DESCRIPTIONS.md#time-naming) | n/a | Conventions around the naming of time variables. | yes | yes |
| [`var-declaration`](./RULES_DESCRIPTIONS.md#var-declaration) | n/a | Reduces redundancies around variable declaration. | yes | yes |
| [`unexported-return`](./RULES_DESCRIPTIONS.md#unexported-return) | n/a | Warns when a public return is from unexported type. | yes | yes |
| [`errorf`](./RULES_DESCRIPTIONS.md#errorf) | n/a | Should replace `errors.New(fmt.Sprintf())` with `fmt.Errorf()` | yes | yes |
| [`blank-imports`](./RULES_DESCRIPTIONS.md#blank-imports) | n/a | Disallows blank imports | yes | no |
| [`context-as-argument`](./RULES_DESCRIPTIONS.md#context-as-argument) | n/a | `context.Context` should be the first argument of a function. | yes | no |
| [`dot-imports`](./RULES_DESCRIPTIONS.md#dot-imports) | n/a | Forbids `.` imports. | yes | no |
| [`error-return`](./RULES_DESCRIPTIONS.md#error-return) | n/a | The error return parameter should be last. | yes | no |
| [`error-strings`](./RULES_DESCRIPTIONS.md#error-strings) | n/a | Conventions around error strings. | yes | no |
| [`error-naming`](./RULES_DESCRIPTIONS.md#error-naming) | n/a | Naming of error variables. | yes | no |
| [`exported`](./RULES_DESCRIPTIONS.md#exported) | n/a | Naming and commenting conventions on exported symbols. | yes | no |
| [`if-return`](./RULES_DESCRIPTIONS.md#if-return) | n/a | Redundant if when returning an error. | yes | no |
| [`increment-decrement`](./RULES_DESCRIPTIONS.md#increment-decrement) | n/a | Use `i++` and `i--` instead of `i += 1` and `i -= 1`. | yes | no |
| [`var-naming`](./RULES_DESCRIPTIONS.md#var-naming) | whitelist & blacklist of initialisms | Naming rules. | yes | no |
| [`package-comments`](./RULES_DESCRIPTIONS.md#package-comments) | n/a | Package commenting conventions. | yes | no |
| [`range`](./RULES_DESCRIPTIONS.md#range) | n/a | Prevents redundant variables when iterating over a collection. | yes | no |
| [`receiver-naming`](./RULES_DESCRIPTIONS.md#receiver-naming) | n/a | Conventions around the naming of receivers. | yes | no |
| [`indent-error-flow`](./RULES_DESCRIPTIONS.md#indent-error-flow) | n/a | Prevents redundant else statements. | yes | no |
| [`argument-limit`](./RULES_DESCRIPTIONS.md#argument-limit) | int | Specifies the maximum number of arguments a function can receive | no | no |
| [`cyclomatic`](./RULES_DESCRIPTIONS.md#cyclomatic) | int | Sets restriction for maximum Cyclomatic complexity. | no | no |
| [`max-public-structs`](./RULES_DESCRIPTIONS.md#max-public-structs) | int | The maximum number of public structs in a file. | no | no |
| [`file-header`](./RULES_DESCRIPTIONS.md#file-header) | string | Header which each file should have. | no | no |
| [`empty-block`](./RULES_DESCRIPTIONS.md#empty-block) | n/a | Warns on empty code blocks | no | no |
| [`superfluous-else`](./RULES_DESCRIPTIONS.md#superfluous-else) | n/a | Prevents redundant else statements (extends [`indent-error-flow`](./RULES_DESCRIPTIONS.md#indent-error-flow)) | no | no |
| [`confusing-naming`](./RULES_DESCRIPTIONS.md#confusing-naming) | n/a | Warns on methods with names that differ only by capitalization | no | no |
| [`get-return`](./RULES_DESCRIPTIONS.md#get-return) | n/a | Warns on getters that do not yield any result | no | no |
| [`modifies-parameter`](./RULES_DESCRIPTIONS.md#modifies-parameter) | n/a | Warns on assignments to function parameters | no | no |
| [`confusing-results`](./RULES_DESCRIPTIONS.md#confusing-results) | n/a | Suggests to name potentially confusing function results | no | no |
| [`deep-exit`](./RULES_DESCRIPTIONS.md#deep-exit) | n/a | Looks for program exits in funcs other than `main()` or `init()` | no | no |
| [`unused-parameter`](./RULES_DESCRIPTIONS.md#unused-parameter) | n/a | Suggests to rename or remove unused function parameters | no | no |
| [`unreachable-code`](./RULES_DESCRIPTIONS.md#unreachable-code) | n/a | Warns on unreachable code | no | no |
| [`add-constant`](./RULES_DESCRIPTIONS.md#add-constant) | map | Suggests using constant for magic numbers and string literals | no | no |
| [`flag-parameter`](./RULES_DESCRIPTIONS.md#flag-parameter) | n/a | Warns on boolean parameters that create a control coupling | no | no |
| [`unnecessary-stmt`](./RULES_DESCRIPTIONS.md#unnecessary-stmt) | n/a | Suggests removing or simplifying unnecessary statements | no | no |
| [`struct-tag`](./RULES_DESCRIPTIONS.md#struct-tag) | n/a | Checks common struct tags like `json`,`xml`,`yaml` | no | no |
| [`modifies-value-receiver`](./RULES_DESCRIPTIONS.md#modifies-value-receiver) | n/a | Warns on assignments to value-passed method receivers | no | yes |
| [`constant-logical-expr`](./RULES_DESCRIPTIONS.md#constant-logical-expr) | n/a | Warns on constant logical expressions | no | no |
| [`bool-literal-in-expr`](./RULES_DESCRIPTIONS.md#bool-literal-in-expr)| n/a | Suggests removing Boolean literals from logic expressions | no | no |
| [`redefines-builtin-id`](./RULES_DESCRIPTIONS.md#redefines-builtin-id)| n/a | Warns on redefinitions of builtin identifiers | no | no |
| [`function-result-limit`](./RULES_DESCRIPTIONS.md#function-result-limit) | int | Specifies the maximum number of results a function can return | no | no |
| [`imports-blacklist`](./RULES_DESCRIPTIONS.md#imports-blacklist) | []string | Disallows importing the specified packages | no | no |
| [`range-val-in-closure`](./RULES_DESCRIPTIONS.md#range-val-in-closure)| n/a | Warns if range value is used in a closure dispatched as goroutine| no | no |
| [`waitgroup-by-value`](./RULES_DESCRIPTIONS.md#waitgroup-by-value) | n/a | Warns on functions taking sync.WaitGroup as a by-value parameter | no | no |
| [`atomic`](./RULES_DESCRIPTIONS.md#atomic) | n/a | Check for common mistaken usages of the `sync/atomic` package | no | no |
| [`empty-lines`](./RULES_DESCRIPTIONS.md#empty-lines) | n/a | Warns when there are heading or trailing newlines in a block | no | no |
| [`line-lenght-limit`](./RULES_DESCRIPTIONS.md#line-lenght-limit) | int | Specifies the maximum number of characters in a line | no | no |
## Configurable rules
Here you can find how you can configure some of the existing rules:
### `var-naming`
This rule accepts two slices of strings, a whitelist and a blacklist of initialisms. By default the rule behaves exactly as the alternative in `golint` but optionally, you can relax it (see [golint/lint/issues/89](https://github.com/golang/lint/issues/89))
```toml
[rule.var-naming]
arguments = [["ID"], ["VM"]]
```
This way, revive will not warn for identifier called `customId` but will warn that `customVm` should be called `customVM`.
## Available Formatters
@ -221,15 +300,29 @@ This section lists all the available formatters and provides a screenshot for ea
### Friendly
![Friendly formatter](/assets/friendly-formatter.png)
![Friendly formatter](/assets/formatter-friendly.png)
### Stylish
![Stylish formatter](/assets/stylish-formatter.png)
![Stylish formatter](/assets/formatter-stylish.png)
### Default
![Default formatter](/assets/default-formatter.png)
The default formatter produces the same output as `golint`.
![Default formatter](/assets/formatter-default.png)
### Plain
The plain formatter produces the same output as the default formatter and appends URL to the rule description.
![Plain formatter](/assets/formatter-plain.png)
### Unix
The unix formatter produces the same output as the default formatter but surrounds the rules in `[]`.
![Unix formatter](/assets/formatter-unix.png)
## Extensibility
@ -321,9 +414,13 @@ Currently, type checking is enabled by default. If you want to run the linter wi
## Contributors
[<img alt="mgechev" src="https://avatars1.githubusercontent.com/u/455023?v=4&s=117" width="117">](https://github.com/mgechev) |[<img alt="chavacava" src="https://avatars2.githubusercontent.com/u/25788468?v=4&s=117" width="117">](https://github.com/chavacava) |[<img alt="tamird" src="https://avatars0.githubusercontent.com/u/1535036?v=4&s=117" width="117">](https://github.com/tamird) |[<img alt="paul-at-start" src="https://avatars2.githubusercontent.com/u/5486775?v=4&s=117" width="117">](https://github.com/paul-at-start) |[<img alt="vkrol" src="https://avatars3.githubusercontent.com/u/153412?v=4&s=117" width="117">](https://github.com/vkrol) |
:---: |:---: |:---: |:---: |:---: |
[mgechev](https://github.com/mgechev) |[chavacava](https://github.com/chavacava) |[tamird](https://github.com/tamird) |[paul-at-start](https://github.com/paul-at-start) |[vkrol](https://github.com/vkrol) |
[<img alt="mgechev" src="https://avatars1.githubusercontent.com/u/455023?v=4&s=117" width="117">](https://github.com/mgechev) |[<img alt="chavacava" src="https://avatars2.githubusercontent.com/u/25788468?v=4&s=117" width="117">](https://github.com/chavacava) |[<img alt="xuri" src="https://avatars2.githubusercontent.com/u/2809468?v=4&s=117" width="117">](https://github.com/xuri) |[<img alt="gsamokovarov" src="https://avatars0.githubusercontent.com/u/604618?v=4&s=117" width="117">](https://github.com/gsamokovarov) |[<img alt="morphy2k" src="https://avatars2.githubusercontent.com/u/4280578?v=4&s=117" width="117">](https://github.com/morphy2k) |[<img alt="z0mbie42" src="https://avatars0.githubusercontent.com/u/6172808?v=4&s=117" width="117">](https://github.com/z0mbie42) |
:---: |:---: |:---: |:---: |:---: |:---: |
[mgechev](https://github.com/mgechev) |[chavacava](https://github.com/chavacava) |[xuri](https://github.com/xuri) |[gsamokovarov](https://github.com/gsamokovarov) |[morphy2k](https://github.com/morphy2k) |[z0mbie42](https://github.com/z0mbie42) |
[<img alt="tamird" src="https://avatars0.githubusercontent.com/u/1535036?v=4&s=117" width="117">](https://github.com/tamird) |[<img alt="paul-at-start" src="https://avatars2.githubusercontent.com/u/5486775?v=4&s=117" width="117">](https://github.com/paul-at-start) |[<img alt="psapezhko" src="https://avatars3.githubusercontent.com/u/10865586?v=4&s=117" width="117">](https://github.com/psapezhko) |[<img alt="Jarema" src="https://avatars0.githubusercontent.com/u/7369771?v=4&s=117" width="117">](https://github.com/Jarema) |[<img alt="vkrol" src="https://avatars3.githubusercontent.com/u/153412?v=4&s=117" width="117">](https://github.com/vkrol) |[<img alt="haya14busa" src="https://avatars0.githubusercontent.com/u/3797062?v=4&s=117" width="117">](https://github.com/haya14busa) |
:---: |:---: |:---: |:---: |:---: |:---: |
[tamird](https://github.com/tamird) |[paul-at-start](https://github.com/paul-at-start) |[psapezhko](https://github.com/psapezhko) |[Jarema](https://github.com/Jarema) |[vkrol](https://github.com/vkrol) |[haya14busa](https://github.com/haya14busa) |
## License

428
RULES_DESCRIPTIONS.md Normal file
View File

@ -0,0 +1,428 @@
# Description of available rules
List of all available rules.
- [Description of available rules](#description-of-available-rules)
- [add-constant](#add-constant)
- [argument-limit](#argument-limit)
- [atomic](#atomic)
- [blank-imports](#blank-imports)
- [bool-literal-in-expr](#bool-literal-in-expr)
- [confusing-naming](#confusing-naming)
- [confusing-results](#confusing-results)
- [constant-logical-expr](#constant-logical-expr)
- [context-as-argument](#context-as-argument)
- [context-keys-type](#context-keys-type)
- [cyclomatic](#cyclomatic)
- [deep-exit](#deep-exit)
- [dot-imports](#dot-imports)
- [empty-block](#empty-block)
- [empty-lines](#empty-lines)
- [error-naming](#error-naming)
- [error-return](#error-return)
- [error-strings](#error-strings)
- [errorf](#errorf)
- [exported](#exported)
- [file-header](#file-header)
- [flag-parameter](#flag-parameter)
- [function-result-limit](#function-result-limit)
- [get-return](#get-return)
- [if-return](#if-return)
- [increment-decrement](#increment-decrement)
- [indent-error-flow](#indent-error-flow)
- [imports-blacklist](#imports-blacklist)
- [line-lenght-limit](#line-lenght-limit)
- [max-public-structs](#max-public-structs)
- [modifies-parameter](#modifies-parameter)
- [modifies-value-receiver](#modifies-value-receiver)
- [package-comments](#package-comments)
- [range](#range)
- [range-val-in-closure](#range-val-in-closure)
- [receiver-naming](#receiver-naming)
- [redefines-builtin-id](#redefines-builtin-id)
- [struct-tag](#struct-tag)
- [superfluous-else](#superfluous-else)
- [time-naming](#time-naming)
- [var-naming](#var-naming)
- [var-declaration](#var-declaration)
- [unexported-return](#unexported-return)
- [unnecessary-stmt](#unnecessary-stmt)
- [unreachable-code](#unreachable-code)
- [unused-parameter](#unused-parameter)
- [waitgroup-by-value](#waitgroup-by-value)
## add-constant
_Description_: Suggests using constant for [magic numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)#Unnamed_numerical_constants) and string literals.
_Configuration_:
* `maxLitCount` : (string) maximum number of instances of a string literal that are tolerated before warn.
* `allowStr`: (string) comma-separated list of allowed string literals
* `allowInts`: (string) comma-separated list of allowed integers
* `allowFloats`: (string) comma-separated list of allowed floats
Example:
```toml
[rule.add-constant]
arguments = [{maxLitCount = "3",allowStrs ="\"\"",allowInts="0,1,2",allowFloats="0.0,0.,1.0,1.,2.0,2."}]
```
## argument-limit
_Description_: Warns when a function receives more parameters than the maximum set by the rule's configuration.
Enforcing a maximum number of parameters helps to keep the code readable and maintainable.
_Configuration_: (int) the maximum number of parameters allowed per function.
Example:
```toml
[argument-limit]
arguments =[4]
```
## atomic
_Description_: Check for commonly mistaken usages of the `sync/atomic` package
_Configuration_: N/A
## blank-imports
_Description_: Blank import should be only in a main or test package, or have a comment justifying it.
_Configuration_: N/A
## bool-literal-in-expr
_Description_: Using Boolean literals (`true`, `false`) in logic expressions may make the code less readable. This rule suggests removing Boolean literals from logic expressions.
_Configuration_: N/A
## confusing-naming
_Description_: Methods or fields of `struct` that have names different only by capitalization could be confusing.
_Configuration_: N/A
## confusing-results
_Description_: Function or methods that return multiple, no named, values of the same type could induce error.
_Configuration_: N/A
## constant-logical-expr
_Description_: The rule spots logical expressions that evaluate always to the same value.
_Configuration_: N/A
## context-as-argument
_Description_: By [convention](https://github.com/golang/go/wiki/CodeReviewComments#contexts), `context.Context` should be the first parameter of a function. This rule spots function declarations that do not follow the convention.
_Configuration_: N/A
## context-keys-type
_Description_: Basic types should not be used as a key in `context.WithValue`.
_Configuration_: N/A
## cyclomatic
_Description_: [Cyclomatic complexity](https://en.wikipedia.org/wiki/Cyclomatic_complexity) is a measure of code complexity. Enforcing a maximum complexity per function helps to keep code readable and maintainable.
_Configuration_: (int) the maximum function complexity
Example:
```toml
[cyclomatic]
arguments =[3]
```
## deep-exit
_Description_: Packages exposing functions that can stop program execution by exiting are hard to reuse. This rule looks for program exits in functions other than `main()` or `init()`.
_Configuration_: N/A
## dot-imports
_Description_: Importing with `.` makes the programs much harder to understand because it is unclear whether names belong to the current package or to an imported package.
More information [here](https://github.com/golang/go/wiki/CodeReviewComments#import-dot)
_Configuration_: N/A
## empty-block
_Description_: Empty blocks make code less readable and could be a symptom of a bug or unfinished refactoring.
_Configuration_: N/A
## empty-lines
_Description_: Sometimes `gofmt` is not enough to enforce a common formatting of a code-base; this rule warns when there are heading or trailing newlines in code blocks.
_Configuration_: N/A
## error-naming
_Description_: By convention, for the sake of readability, variables of type `error` must be named with the prefix `err`.
_Configuration_: N/A
## error-return
_Description_: By convention, for the sake of readability, the errors should be last in the list of returned values by a function.
_Configuration_: N/A
## error-strings
_Description_: By convention, for better readability, error messages should not be capitalized or end with punctuation or a newline.
More information [here](https://github.com/golang/go/wiki/CodeReviewComments#error-strings)
_Configuration_: N/A
## errorf
_Description_: It is possible to get a simpler program by replacing `errors.New(fmt.Sprintf())` with `fmt.Errorf()`. This rule spots that kind of simplification opportunities.
_Configuration_: N/A
## exported
_Description_: Exported function and methods should have comments. This warns on undocumented exported functions and methods.
More information [here](https://github.com/golang/go/wiki/CodeReviewComments#doc-comments)
_Configuration_: N/A
## file-header
_Description_: This rule helps to enforce a common header for all source files in a project by spotting those files that do not have the specified header.
_Configuration_: (string) the header to look for in source files.
Example:
```toml
[file-header]
arguments =["This is the text that must appear at the top of source files."]
```
## flag-parameter
_Description_: If a function controls the flow of another by passing it information on what to do, both functions are said to be [control-coupled](https://en.wikipedia.org/wiki/Coupling_(computer_programming)#Procedural_programming).
Coupling among functions must be minimized for better maintainability of the code.
This rule warns on boolean parameters that create a control coupling.
_Configuration_: N/A
## function-result-limit
_Description_: Functions returning too many results can be hard to understand/use.
_Configuration_: (int) the maximum allowed return values
Example:
```toml
[function-result-limit]
arguments =[3]
```
## get-return
_Description_: Typically, functions with names prefixed with _Get_ are supposed to return a value.
_Configuration_: N/A
## if-return
_Description_: Checking if an error is _nil_ to just after return the error or nil is redundant.
_Configuration_: N/A
## increment-decrement
_Description_: By convention, for better readability, incrementing an integer variable by 1 is recommended to be done using the `++` operator.
This rule spots expressions like `i += 1` and `i -= 1` and proposes to change them into `i++` and `i--`.
_Configuration_: N/A
## indent-error-flow
_Description_: To improve the readability of code, it is recommended to reduce the indentation as much as possible.
This rule highlights redundant _else-blocks_ that can be eliminated from the code.
More information [here](https://github.com/golang/go/wiki/CodeReviewComments#indent-error-flow)
_Configuration_: N/A
## imports-blacklist
_Description_: Warns when importing black-listed packages.
_Configuration_: black-list of package names
Example:
```toml
[imports-blacklist]
arguments =["crypto/md5", "crypto/sha1"]
```
## line-lenght-limit
_Description_: Warns in the presence of code lines longer than a configured maximum.
_Configuration_: (int) maximum line length in characters.
Example:
```toml
[line-lenght-limit]
arguments =[80]
```
## max-public-structs
_Description_: Packages declaring too many public structs can be hard to understand/use,
and could be a symptom of bad design.
This rule warns on files declaring more than a configured, maximum number of public structs.
_Configuration_: (int) the maximum allowed public structs
Example:
```toml
[max-public-structs]
arguments =[3]
```
## modifies-parameter
_Description_: A function that modifies its parameters can be hard to understand. It can also be misleading if the arguments are passed by value by the caller.
This rule warns when a function modifies one or more of its parameters.
_Configuration_: N/A
## modifies-value-receiver
_Description_: A method that modifies its receiver value can have undesired behavior. The modification can be also the root of a bug because the actual value receiver could be a copy of that used at the calling site.
This rule warns when a method modifies its receiver.
_Configuration_: N/A
## package-comments
_Description_: Packages should have comments. This rule warns on undocumented packages and when packages comments are detached to the `package` keyword.
More information [here](https://github.com/golang/go/wiki/CodeReviewComments#package-comments)
_Configuration_: N/A
## range
_Description_: This rule suggests a shorter way of writing ranges that do not use the second value.
_Configuration_: N/A
## range-val-in-closure
_Description_: Range variables in a loop are reused at each iteration; therefore a goroutine created in a loop will point to the range variable with from the upper scope. This way, the goroutine could use the variable with an undesired value.
This rule warns when a range value (or index) is used inside a closure
_Configuration_: N/A
## receiver-naming
_Description_: By convention, receiver names in a method should reflect their identity. For example, if the receiver is of type `Parts`, `p` is an adequate name for it. Contrary to other languages, it is not idiomatic to name receivers as `this` or `self`.
_Configuration_: N/A
## redefines-builtin-id
_Description_: Constant names like `false`, `true`, `nil`, function names like `append`, `make`, and basic type names like `bool`, and `byte` are not reserved words of the language; therefore the can be redefined.
Even if possible, redefining these built in names can lead to bugs very difficult to detect.
_Configuration_: N/A
## struct-tag
_Description_: Struct tags are not checked at compile time.
This rule, checks and warns if it finds errors in common struct tags types like: asn1, default, json, protobuf, xml, yaml.
_Configuration_: N/A
## superfluous-else
_Description_: To improve the readability of code, it is recommended to reduce the indentation as much as possible.
This rule highlights redundant _else-blocks_ that can be eliminated from the code.
_Configuration_: N/A
## time-naming
_Description_: Using unit-specific suffix like "Secs", "Mins", ... when naming variables of type `time.Duration` can be misleading, this rule highlights those cases.
_Configuration_: N/A
## var-naming
_Description_: This rule warns when [variable](https://github.com/golang/go/wiki/CodeReviewComments#variable-names) or [package](https://github.com/golang/go/wiki/CodeReviewComments#package-names) naming conventions are not followed.
_Configuration_: This rule accepts two slices of strings, a whitelist and a blacklist of initialisms. By default, the rule behaves exactly as the alternative in `golint` but optionally, you can relax it (see [golint/lint/issues/89](https://github.com/golang/lint/issues/89))
Example:
```toml
[rule.var-naming]
arguments = [["ID"], ["VM"]]
```
## var-declaration
_Description_: This rule proposes simplifications of variable declarations.
_Configuration_: N/A
## unexported-return
_Description_: This rule warns when an exported function or method returns a value of an un-exported type.
_Configuration_: N/A
## unnecessary-stmt
_Description_: This rule suggests to remove redundant statements like a `break` at the end of a case block, for improving the code's readability.
_Configuration_: N/A
## unreachable-code
_Description_: This rule spots and proposes to remove [unreachable code](https://en.wikipedia.org/wiki/Unreachable_code).
_Configuration_: N/A
## unused-parameter
_Description_: This rule warns on unused parameters. Functions or methods with unused parameters can be a symptom of an unfinished refactoring or a bug.
_Configuration_: N/A
## waitgroup-by-value
_Description_: Function parameters that are passed by value, are in fact a copy of the original argument. Passing a copy of a `sync.WaitGroup` is usually not what the developer wants to do.
This rule warns when a `sync.WaitGroup` expected as a by-value parameter in a function or method.
_Configuration_: N/A

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

BIN
assets/formatter-plain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

BIN
assets/formatter-unix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

View File

@ -49,18 +49,45 @@ var allRules = append([]lint.Rule{
&rule.FileHeaderRule{},
&rule.EmptyBlockRule{},
&rule.SuperfluousElseRule{},
&rule.ConfusingNamingRule{},
&rule.GetReturnRule{},
&rule.ModifiesParamRule{},
&rule.ConfusingResultsRule{},
&rule.DeepExitRule{},
<<<<<<< HEAD
&rule.ADSPrintRule{},
&rule.ADSLostErrRule{},
=======
&rule.UnusedParamRule{},
&rule.UnreachableCodeRule{},
&rule.AddConstantRule{},
&rule.FlagParamRule{},
&rule.UnnecessaryStmtRule{},
&rule.StructTagRule{},
&rule.ModifiesValRecRule{},
&rule.ConstantLogicalExprRule{},
&rule.BoolLiteralRule{},
&rule.RedefinesBuiltinIDRule{},
&rule.ImportsBlacklistRule{},
&rule.FunctionResultsLimitRule{},
&rule.MaxPublicStructsRule{},
&rule.RangeValInClosureRule{},
&rule.WaitGroupByValueRule{},
&rule.AtomicRule{},
&rule.EmptyLinesRule{},
&rule.LineLengthLimitRule{},
>>>>>>> ad7df7dc375879b9c25628acf9d643378cfaf102
}, defaultRules...)
var allFormatters = []lint.Formatter{
&formatter.Stylish{},
&formatter.Friendly{},
&formatter.JSON{},
&formatter.NDJSON{},
&formatter.Default{},
&formatter.Unix{},
&formatter.Checkstyle{},
&formatter.Plain{},
}
func getFormatters() map[string]lint.Formatter {

View File

@ -22,4 +22,4 @@ warningCode = 0
[rule.time-naming]
[rule.unexported-return]
[rule.indent-error-flow]
[rule.errorf]
[rule.errorf]

16
fixtures/add-constant.go Normal file
View File

@ -0,0 +1,16 @@
package fixtures
func foo(a, b, c, d int) {
a = 1.0 // ignore
b = "ignore"
c = 2 // ignore
println("lit", 12) // MATCH /avoid magic numbers like '12', create a named constant for it/
if a == 12.50 { // MATCH /avoid magic numbers like '12.50', create a named constant for it/
if b == "lit" {
c = "lit" // MATCH /string literal "lit" appears, at least, 3 times, create a named constant for it/
}
for i := 0; i < 1; i++ {
println("lit")
}
}
}

45
fixtures/atomic.go Normal file
View File

@ -0,0 +1,45 @@
package fixtures
import (
"sync/atomic"
)
type Counter uint64
func AtomicTests() {
x := uint64(1)
x = atomic.AddUint64(&x, 1) // MATCH /direct assignment to atomic value/
_, x = 10, atomic.AddUint64(&x, 1) // MATCH /direct assignment to atomic value/
x, _ = atomic.AddUint64(&x, 1), 10 // MATCH /direct assignment to atomic value/
y := &x
*y = atomic.AddUint64(y, 1) // MATCH /direct assignment to atomic value/
var su struct{ Counter uint64 }
su.Counter = atomic.AddUint64(&su.Counter, 1) // MATCH /direct assignment to atomic value/
z1 := atomic.AddUint64(&su.Counter, 1)
_ = z1 // Avoid err "z declared and not used"
var sp struct{ Counter *uint64 }
*sp.Counter = atomic.AddUint64(sp.Counter, 1) // MATCH /direct assignment to atomic value/
z2 := atomic.AddUint64(sp.Counter, 1)
_ = z2 // Avoid err "z declared and not used"
au := []uint64{10, 20}
au[0] = atomic.AddUint64(&au[0], 1) // MATCH /direct assignment to atomic value/
au[1] = atomic.AddUint64(&au[0], 1)
ap := []*uint64{&au[0], &au[1]}
*ap[0] = atomic.AddUint64(ap[0], 1) // MATCH /direct assignment to atomic value/
*ap[1] = atomic.AddUint64(ap[0], 1)
}
type T struct{}
func (T) AddUint64(addr *uint64, delta uint64) uint64 { return 0 }
func NonAtomic() {
x := uint64(1)
var atomic T
x = atomic.AddUint64(&x, 1) // MATCH /direct assignment to atomic value/
}

View File

@ -0,0 +1,54 @@
package fixtures
func foo(a, b, c, d int) bool {
if bar == true { // MATCH /omit Boolean literal in expression/
}
for f() || false != yes { // MATCH /omit Boolean literal in expression/
}
return b > c == false // MATCH /omit Boolean literal in expression/
}
// from github.com/jmespath/go-jmespath/functions.go
func jpfToNumber(arguments []interface{}) (interface{}, error) {
arg := arguments[0]
// code skipped
if arg == true || // MATCH /omit Boolean literal in expression/
arg == false { // MATCH /omit Boolean literal in expression/
return nil, nil
}
return nil, errors.New("unknown type")
}
// from gopkg.in/yaml.v2/resolve.go
func resolve(tag string, in string) (rtag string, out interface{}) {
if err == nil {
if true || intv == int64(int(intv)) { // MATCH /Boolean expression seems to always evaluate to true/
return yaml_INT_TAG, int(intv)
} else {
return yaml_INT_TAG, intv
}
}
}
// from github.com/miekg/dns/msg_helpers.go
func packDataDomainNames(names []string, msg []byte, off int, compression map[string]int, compress bool) (int, error) {
var err error
for j := 0; j < len(names); j++ {
off, err = PackDomainName(names[j], msg, off, compression, false && compress) // MATCH /Boolean expression seems to always evaluate to false/
if err != nil {
return len(msg), err
}
}
return off, nil
}
func isTrue(arg bool) bool {
return arg
}
func main() {
isTrue(true)
}

View File

@ -0,0 +1,62 @@
// Test of confusing-naming rule.
// Package pkg ...
package pkg
type foo struct{}
func (t foo) aFoo() {
return
}
func (t *foo) AFoo() { // MATCH /Method 'AFoo' differs only by capitalization to method 'aFoo' in the same source file/
return
}
type bar struct{}
func (t *bar) aBar() {
return
}
func (t *bar) aFoo() { // Should not warn
return
}
func aGlobal() {
}
func AGlobal() { // MATCH /Method 'AGlobal' differs only by capitalization to function 'aGlobal' in the same source file/
}
func ABar() { // Should not warn
}
func aFoo() { // Should not warn
}
func (t foo) ABar() { // Should not warn
return
}
func (t bar) ABar() { // MATCH /Method 'ABar' differs only by capitalization to method 'aBar' in the same source file/
return
}
func x() {}
type tFoo struct {
asd string
aSd int // MATCH /Field 'aSd' differs only by capitalization to other field in the struct type tFoo/
qwe, asD bool // MATCH /Field 'asD' differs only by capitalization to other field in the struct type tFoo/
zxc float32
}
type tBar struct {
asd string
qwe bool
zxc float32
}

View File

@ -0,0 +1,7 @@
// Test of confusing-naming rule.
// Package pkg ...
package pkg
func aglobal() { // MATCH /Function 'aglobal' differs only by capitalization to other function in the same package/
}

View File

@ -0,0 +1,20 @@
package fixtures
func getfoo() (int, int, error) { // MATCH /unnamed results of the same type may be confusing, consider using named results/
}
func getBar(a, b int) (int, error, int) {
}
func Getbaz(a string, b int) (int, float32, string, string) { // MATCH /unnamed results of the same type may be confusing, consider using named results/
}
func GetTaz(a string, b int) string {
}
func (t *t) GetTaz(a int, b int) {
}

View File

@ -0,0 +1,26 @@
package fixtures
// from github.com/ugorji/go/codec/helper.go
func isNaN(f float64) bool { return f != f } // MATCH /expression always evaluates to false/
func skip(f float64) bool { return f != g }
func foo1(f float64) bool { return foo2(2.) > foo2(2.) } // MATCH /expression always evaluates to false/
func foo2(f float64) bool { return f < f } // MATCH /expression always evaluates to false/
func foo3(f float64) bool { return f <= f } // MATCH /expression always evaluates to false/
func foo4(f float64) bool { return f >= f } // MATCH /expression always evaluates to false/
func foo5(f float64) bool { return f == f } // MATCH /expression always evaluates to true/
func foo6(f float64) bool { return fmt.Sprintf("%s", buf1.Bytes()) == fmt.Sprintf("%s", buf1.Bytes()) } // MATCH /expression always evaluates to true/
func foo7(f float64) bool {
return fFoo(fBar(isNaN(10.), bpar), 10000) || fFoo(fBar(isNaN(10.), bpar), 10000) // MATCH /left and right hand-side sub-expressions are the same/
}
func foo8(f float64) bool {
return fFoo(fBar(isNaN(10.), bpar), 10000) && fFoo(fBar(isNaN(10.), bpar), 10000) // MATCH /left and right hand-side sub-expressions are the same/
}

162
fixtures/empty-lines.go Normal file
View File

@ -0,0 +1,162 @@
// Test of empty-lines.
package fixtures
import "time"
func f1(x *int) bool { // MATCH /extra empty line at the start of a block/
return x > 2
}
func f2(x *int) bool {
return x > 2 // MATCH /extra empty line at the end of a block/
}
func f3(x *int) bool { // MATCH /extra empty line at the start of a block/
return x > 2 // MATCH /extra empty line at the end of a block/
}
func f4(x *int) bool {
// This is fine.
return x > 2
}
func f5(x *int) bool { // MATCH /extra empty line at the start of a block/
// This is _not_ fine.
return x > 2
}
func f6(x *int) bool {
return x > 2
// This is fine.
}
func f7(x *int) bool {
return x > 2 // MATCH /extra empty line at the end of a block/
// This is _not_ fine.
}
func f8(*int) bool {
if x > 2 { // MATCH /extra empty line at the start of a block/
return true
}
return false
}
func f9(*int) bool {
if x > 2 {
return true // MATCH /extra empty line at the end of a block/
}
return false
}
func f10(*int) bool { // MATCH /extra empty line at the start of a block/
if x > 2 {
return true
}
return false
}
func f11(x *int) bool {
if x > 2 {
return true
}
}
func f12(x *int) bool {
if x > 2 { // MATCH /extra empty line at the end of a block/
return true
}
}
func f13(x *int) bool {
switch {
case x == 2:
return false
}
}
func f14(x *int) bool {
switch { // MATCH /extra empty line at the end of a block/
case x == 2:
return false
}
}
func f15(x *int) bool {
switch {
case x == 2: // MATCH /extra empty line at the end of a block/
return false
}
}
func f16(x *int) bool {
return Query(
qm("x = ?", x),
).Execute()
}
func f17(x *int) bool {
return Query( // MATCH /extra empty line at the end of a block/
qm("x = ?", x),
).Execute()
}
func f18(x *int) bool {
if true {
if true { // MATCH /extra empty line at the end of a block/
return true
}
// TODO: should we handle the error here?
}
return false
}
func w() {
select {
case <-time.After(dur):
// TODO: Handle Ctrl-C is pressed in `mysql` client.
// return 1 when SLEEP() is KILLed
}
return 0, false, nil
}
func x() {
if tagArray[2] == "req" {
bit := len(u.reqFields)
u.reqFields = append(u.reqFields, name)
reqMask = uint64(1) << uint(bit)
// TODO: if we have more than 64 required fields, we end up
// not verifying that all required fields are present.
// Fix this, perhaps using a count of required fields?
}
if err == nil { // No need to refresh if the stream is over or failed.
// Consider any buffered body data (read from the conn but not
// consumed by the client) when computing flow control for this
// stream.
v := int(cs.inflow.available()) + cs.bufPipe.Len()
if v < transportDefaultStreamFlow-transportDefaultStreamMinRefresh {
streamAdd = int32(transportDefaultStreamFlow - v)
cs.inflow.add(streamAdd)
}
}
}

11
fixtures/flag-param.go Normal file
View File

@ -0,0 +1,11 @@
package fixtures
func foo(a bool, b int) { // MATCH /parameter 'a' seems to be a control flag, avoid control coupling/
if a {
}
}
func foo(a bool, b int) {
str := mystruct{a, b}
}

View File

@ -0,0 +1,17 @@
package fixtures
func foo() (a, b, c, d) { // MATCH /maximum number of return results per function exceeded; max 3 but got 4/
var a, b, c, d int
}
func bar(a, b int) {
}
func baz(a string, b int) {
}
func qux() (string, string, int, string, int) { // MATCH /maximum number of return results per function exceeded; max 3 but got 5/
}

View File

@ -0,0 +1,15 @@
// Package golint comment
package golint
type (
// O is a shortcut (alias) for map[string]interface{}, e.g. a JSON object.
O = map[string]interface{}
// A is shortcut for []O.
A = []O
// This Person type is simple
Person = map[string]interface{}
)
type Foo struct{} // MATCH /exported type Foo should have comment or be unexported/

View File

@ -0,0 +1,7 @@
package fixtures
import (
"crypto/md5" // MATCH /should not use the following blacklisted import: "crypto/md5"/
"crypto/sha1" // MATCH /should not use the following blacklisted import: "crypto/sha1"/
"strings"
)

View File

@ -0,0 +1,7 @@
package fixtures
import "fmt"
func foo(a, b int) {
fmt.Printf("single line characters out of limit") // MATCH /line is 105 characters, out of limit 100/
}

View File

@ -0,0 +1,13 @@
package fixtures
type data struct {
num int
key *string
items map[string]bool
}
func (this data) vmethod() {
this.num = 8 // MATCH /suspicious assignment to a by-value method receiver/
*this.key = "v.key"
this.items["vmethod"] = true
}

View File

@ -0,0 +1,37 @@
package fixtures
import "fmt"
func foo() {
mySlice := []string{"A", "B", "C"}
for index, value := range mySlice {
go func() {
fmt.Printf("Index: %d\n", index) // MATCH /loop variable index captured by func literal/
fmt.Printf("Value: %s\n", value) // MATCH /loop variable value captured by func literal/
}()
}
myDict := make(map[string]int)
myDict["A"] = 1
myDict["B"] = 2
myDict["C"] = 3
for key, value := range myDict {
defer func() {
fmt.Printf("Index: %d\n", key) // MATCH /loop variable key captured by func literal/
fmt.Printf("Value: %s\n", value) // MATCH /loop variable value captured by func literal/
}()
}
for i, newg := range groups {
go func(newg int) {
newg.run(m.opts.Context,i) // MATCH /loop variable i captured by func literal/
}(newg)
}
for i, newg := range groups {
newg := newg
go func() {
newg.run(m.opts.Context,i) // MATCH /loop variable i captured by func literal/
}()
}
}

View File

@ -0,0 +1,21 @@
package fixtures
func (this data) vmethod() {
nil := true // MATCH /assignment creates a shadow of built-in identifier nil/
iota = 1 // MATCH /assignment modifies built-in identifier iota/
}
func append(i, j int) { // MATCH /redefinition of the built-in function append/
}
type Type int16 // MATCH /redefinition of the built-in type Type/
func delete(set []int64, i int) (y []int64) { // MATCH /redefinition of the built-in function delete/
for j, v := range set {
if j != i {
y = append(y, v)
}
}
return
}

53
fixtures/struct-tag.go Normal file
View File

@ -0,0 +1,53 @@
package fixtures
type decodeAndValidateRequest struct {
// BEAWRE : the flag of URLParam should match the const string URLParam
URLParam string `json:"-" path:"url_param" validate:"numeric"`
Text string `json:"text" validate:"max=10"`
DefaultInt int `json:"defaultInt" default:"10.0"` // MATCH /field's type and default value's type mismatch/
DefaultInt2 int `json:"defaultInt" default:"10"`
DefaultString string `json:"defaultString" default:"foo"`
DefaultBool bool `json:"defaultBool" default:"trues"` // MATCH /field's type and default value's type mismatch/
DefaultBool2 bool `json:"defaultBool" default:"true"`
DefaultBool3 bool `json:"defaultBool" default:"false"`
DefaultFloat float64 `json:"defaultFloat" default:"f10.0"` // MATCH /field's type and default value's type mismatch/
DefaultFloat2 float64 `json:"defaultFloat" default:"10.0"`
MandatoryStruct mandatoryStruct `json:"mandatoryStruct" required:"trues"` // MATCH /required should be 'true' or 'false'/
MandatoryStruct2 mandatoryStruct `json:"mandatoryStruct" required:"true"`
MandatoryStruct4 mandatoryStruct `json:"mandatoryStruct" required:"false"`
OptionalStruct *optionalStruct `json:"optionalStruct,omitempty"`
OptionalQuery string `json:"-" querystring:"queryfoo"`
}
type RangeAllocation struct {
metav1.TypeMeta `json:",inline"` // MATCH /unknown option 'inline' in JSON tag/
metav1.ObjectMeta `json:"metadata,omitempty"`
Range string `json:"range,flow"` // MATCH /unknown option 'flow' in JSON tag/
Data []byte `json:"data,inline"` // MATCH /unknown option 'inline' in JSON tag/
}
type RangeAllocation struct {
metav1.TypeMeta `bson:",minsize"`
metav1.ObjectMeta `bson:"metadata,omitempty"`
Range string `bson:"range,flow"` // MATCH /unknown option 'flow' in BSON tag/
Data []byte `bson:"data,inline"`
}
type TestContextSpecificTags2 struct {
A int `asn1:"explicit,tag:1"`
B int `asn1:"tag:2"`
S string `asn1:"tag:0,utf8"`
Ints []int `asn1:"set"`
Version int `asn1:"optional,explicit,default:0,tag:0"` // MATCH /duplicated tag number 0/
Time time.Time `asn1:"explicit,tag:4,other"` // MATCH /unknown option 'other' in ASN1 tag/
}
type VirtualMachineRelocateSpecDiskLocator struct {
DynamicData
DiskId int32 `xml:"diskId,attr,cdata"`
Datastore ManagedObjectReference `xml:"datastore,chardata,innerxml"`
DiskMoveType string `xml:"diskMoveType,omitempty,comment"`
DiskBackingInfo BaseVirtualDeviceBackingInfo `xml:"diskBackingInfo,omitempty,any"`
Profile []BaseVirtualMachineProfileSpec `xml:"profile,omitempty,other"` // MATCH /unknown option 'other' in XML tag/
}

View File

@ -0,0 +1,49 @@
package fixtures
func foo(a, b, c, d int) {
switch n := node.(type) { // MATCH /switch with only one case can be replaced by an if-then/
case *ast.SwitchStmt:
caseSelector := func(n ast.Node) bool {
_, ok := n.(*ast.CaseClause)
return ok
}
cases := pick(n.Body, caseSelector, nil)
if len(cases) == 1 {
cs, ok := cases[0].(*ast.CaseClause)
if ok && len(cs.List) == 1 {
w.onFailure(lint.Failure{
Confidence: 1,
Node: n,
Category: "style",
Failure: "switch can be replaced by an if-then",
})
}
}
}
}
func bar() {
a := 1
switch a {
case 1, 2:
a++
}
loop:
for {
switch a {
case 1:
a++
println("one")
break // MATCH /omit unnecessary break at the end of case clause/
case 2:
println("two")
break loop
default:
println("default")
}
}
return // MATCH /omit unnecessary return statement/
}

View File

@ -0,0 +1,37 @@
package fixtures
import (
"fmt"
"log"
"os"
)
func foo() int {
log.Fatalf("%s", "About to fail") // ignore
return 0 // MATCH /unreachable code after this statement/
return 1
Println("unreachable")
}
func f() {
fmt.Println("Hello, playground")
if true {
return // MATCH /unreachable code after this statement/
Println("unreachable")
os.Exit(2) // ignore
Println("also unreachable")
}
return // MATCH /unreachable code after this statement/
fmt.Println("Bye, playground")
}
func g() {
fmt.Println("Hello, playground")
if true {
return // ignore if next stmt is labeled
label:
os.Exit(2) // ignore
}
fmt.Println("Bye, playground")
}

169
fixtures/unused-param.go Normal file
View File

@ -0,0 +1,169 @@
package fixtures
import (
"fmt"
"go/ast"
"io/ioutil"
"os"
"runtime"
"testing"
"time"
"github.com/mgechev/revive/lint"
)
func f0(param int) {
param := param
}
func f1(param int) { // MATCH /parameter 'param' seems to be unused, consider removing or renaming it as _/
if param := fn(); predicate(param) {
// do stuff
}
}
func f2(param int) { // MATCH /parameter 'param' seems to be unused, consider removing or renaming it as _/
switch param := fn(); param {
default:
}
}
func f3(param myStruct) {
a := param.field
}
func f4(param myStruct, c int) { // MATCH /parameter 'c' seems to be unused, consider removing or renaming it as _/
param.field = "aString"
param.c = "sss"
}
func f5(a int, _ float) { // MATCH /parameter 'a' seems to be unused, consider removing or renaming it as _/
fmt.Printf("Hello, Golang\n")
{
if true {
a := 2
b := a
}
}
}
func f6(unused string) { // MATCH /parameter 'unused' seems to be unused, consider removing or renaming it as _/
switch unused := runtime.GOOS; unused {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.", unused)
}
for unused := 0; unused < 10; unused++ {
sum += unused
}
{
unused := 1
}
}
func f6bis(unused string) {
switch unused := runtime.GOOS; unused {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.", unused)
}
for unused := 0; unused < 10; unused++ {
sum += unused
}
{
unused := 1
}
fmt.Print(unused)
}
func f7(pl int) {
for i := 0; pl < i; i-- {
}
}
func getCompareFailCause(n *node, which int, prevValue string, prevIndex uint64) string {
switch which {
case CompareIndexNotMatch:
return fmt.Sprintf("[%v != %v]", prevIndex, n.ModifiedIndex)
case CompareValueNotMatch:
return fmt.Sprintf("[%v != %v]", prevValue, n.Value)
default:
return fmt.Sprintf("[%v != %v] [%v != %v]", prevValue, n.Value, prevIndex, n.ModifiedIndex)
}
}
func assertSuccess(t *testing.T, baseDir string, fi os.FileInfo, src []byte, rules []lint.Rule, config map[string]lint.RuleConfig) error { // MATCH /parameter 'src' seems to be unused, consider removing or renaming it as _/
l := lint.New(func(file string) ([]byte, error) {
return ioutil.ReadFile(baseDir + file)
})
ps, err := l.Lint([][]string{[]string{fi.Name()}}, rules, lint.Config{
Rules: config,
})
if err != nil {
return err
}
failures := ""
for p := range ps {
failures += p.Failure
}
if failures != "" {
t.Errorf("Expected the rule to pass but got the following failures: %s", failures)
}
return nil
}
func (w lintCyclomatic) Visit(n ast.Node) ast.Visitor { // MATCH /parameter 'n' seems to be unused, consider removing or renaming it as _/
f := w.file
for _, decl := range f.AST.Decls {
if fn, ok := decl.(*ast.FuncDecl); ok {
c := complexity(fn)
if c > w.complexity {
w.onFailure(lint.Failure{
Confidence: 1,
Category: "maintenance",
Failure: fmt.Sprintf("function %s has cyclomatic complexity %d", funcName(fn), c),
Node: fn,
})
}
}
}
return nil
}
func ext۰time۰Sleep(fr *frame, args []value) value { // MATCH /parameter 'fr' seems to be unused, consider removing or renaming it as _/
time.Sleep(time.Duration(args[0].(int64)))
return nil
}
func (c *chanList) remove(id uint32) {
id -= c.offset
}
func (c *chanList) remove1(id uint32) {
id *= c.offset
}
func (c *chanList) remove2(id uint32) {
id /= c.offset
}
func (c *chanList) remove3(id uint32) {
id += c.offset
}
func encodeFixed64Rpc(dAtA []byte, offset int, v uint64, i int) int {
dAtA[offset+i] = uint8(v)
return 8
}

7
fixtures/var-naming.go Normal file
View File

@ -0,0 +1,7 @@
package fixtures
func foo() string {
customId := "result"
customVm := "result" // MATCH /var customVm should be customVM/
return customId
}

View File

@ -0,0 +1,21 @@
package fixtures
import (
"sync"
)
func foo(a int, b float32, c char, d sync.WaitGroup) { // MATCH /sync.WaitGroup passed by value, the function will get a copy of the original one/
}
func bar(a, b sync.WaitGroup) { // MATCH /sync.WaitGroup passed by value, the function will get a copy of the original one/
}
func baz(zz sync.WaitGroup) { // MATCH /sync.WaitGroup passed by value, the function will get a copy of the original one/
}
func ok(zz *sync.WaitGroup) {
}

76
formatter/checkstyle.go Normal file
View File

@ -0,0 +1,76 @@
package formatter
import (
"bytes"
"encoding/xml"
"github.com/mgechev/revive/lint"
plainTemplate "text/template"
)
// Checkstyle is an implementation of the Formatter interface
// which formats the errors to Checkstyle-like format.
type Checkstyle struct {
Metadata lint.FormatterMetadata
}
// Name returns the name of the formatter
func (f *Checkstyle) Name() string {
return "checkstyle"
}
type issue struct {
Line int
Col int
What string
Confidence float64
Severity lint.Severity
RuleName string
}
// Format formats the failures gotten from the lint.
func (f *Checkstyle) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
var issues = map[string][]issue{}
for failure := range failures {
buf := new(bytes.Buffer)
xml.Escape(buf, []byte(failure.Failure))
what := buf.String()
iss := issue{
Line: failure.Position.Start.Line,
Col: failure.Position.Start.Column,
What: what,
Confidence: failure.Confidence,
Severity: severity(config, failure),
RuleName: failure.RuleName,
}
fn := failure.GetFilename()
if issues[fn] == nil {
issues[fn] = make([]issue, 0)
}
issues[fn] = append(issues[fn], iss)
}
t, err := plainTemplate.New("revive").Parse(checkstyleTemplate)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
err = t.Execute(buf, issues)
if err != nil {
return "", err
}
return buf.String(), nil
}
const checkstyleTemplate = `<?xml version='1.0' encoding='UTF-8'?>
<checkstyle version="5.0">
{{- range $k, $v := . }}
<file name="{{ $k }}">
{{- range $i, $issue := $v }}
<error line="{{ $issue.Line }}" column="{{ $issue.Col }}" message="{{ $issue.What }} (confidence {{ $issue.Confidence}})" severity="{{ $issue.Severity }}" source="revive/{{ $issue.RuleName }}"/>
{{- end }}
</file>
{{- end }}
</checkstyle>`

View File

@ -7,7 +7,7 @@ import (
)
// Default is an implementation of the Formatter interface
// which formats the errors to JSON.
// which formats the errors to text.
type Default struct {
Metadata lint.FormatterMetadata
}
@ -18,7 +18,7 @@ func (f *Default) Name() string {
}
// Format formats the failures gotten from the lint.
func (f *Default) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
func (f *Default) Format(failures <-chan lint.Failure, _ lint.RulesConfig) (string, error) {
for failure := range failures {
fmt.Printf("%v: %s\n", failure.Position.Start, failure.Failure)
}

View File

@ -72,7 +72,7 @@ func (f *Friendly) printHeaderRow(failure lint.Failure, severity lint.Severity)
if severity == lint.SeverityError {
emoji = errorEmoji
}
fmt.Print(f.table([][]string{{emoji, failure.RuleName, color.GreenString(failure.Failure)}}))
fmt.Print(f.table([][]string{{emoji, "https://revive.run/r#" + failure.RuleName, color.GreenString(failure.Failure)}}))
}
func (f *Friendly) printFilePosition(failure lint.Failure) {

View File

@ -17,11 +17,20 @@ func (f *JSON) Name() string {
return "json"
}
// jsonObject defines a JSON object of an failure
type jsonObject struct {
Severity lint.Severity
lint.Failure `json:",inline"`
}
// Format formats the failures gotten from the lint.
func (f *JSON) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
var slice []lint.Failure
var slice []jsonObject
for failure := range failures {
slice = append(slice, failure)
obj := jsonObject{}
obj.Severity = severity(config, failure)
obj.Failure = failure
slice = append(slice, obj)
}
result, err := json.Marshal(slice)
if err != nil {

34
formatter/ndjson.go Normal file
View File

@ -0,0 +1,34 @@
package formatter
import (
"encoding/json"
"os"
"github.com/mgechev/revive/lint"
)
// NDJSON is an implementation of the Formatter interface
// which formats the errors to NDJSON stream.
type NDJSON struct {
Metadata lint.FormatterMetadata
}
// Name returns the name of the formatter
func (f *NDJSON) Name() string {
return "ndjson"
}
// Format formats the failures gotten from the lint.
func (f *NDJSON) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
enc := json.NewEncoder(os.Stdout)
for failure := range failures {
obj := jsonObject{}
obj.Severity = severity(config, failure)
obj.Failure = failure
err := enc.Encode(obj)
if err != nil {
return "", err
}
}
return "", nil
}

26
formatter/plain.go Normal file
View File

@ -0,0 +1,26 @@
package formatter
import (
"fmt"
"github.com/mgechev/revive/lint"
)
// Plain is an implementation of the Formatter interface
// which formats the errors to JSON.
type Plain struct {
Metadata lint.FormatterMetadata
}
// Name returns the name of the formatter
func (f *Plain) Name() string {
return "plain"
}
// Format formats the failures gotten from the lint.
func (f *Plain) Format(failures <-chan lint.Failure, _ lint.RulesConfig) (string, error) {
for failure := range failures {
fmt.Printf("%v: %s %s\n", failure.Position.Start, failure.Failure, "https://revive.run/r#"+failure.RuleName)
}
return "", nil
}

View File

@ -22,11 +22,11 @@ func (f *Stylish) Name() string {
func formatFailure(failure lint.Failure, severity lint.Severity) []string {
fString := color.CyanString(failure.Failure)
fName := color.RedString(failure.RuleName)
fName := color.RedString("https://revive.run/r#" + failure.RuleName)
lineColumn := failure.Position
pos := fmt.Sprintf("(%d, %d)", lineColumn.Start.Line, lineColumn.Start.Column)
if severity == lint.SeverityWarning {
fName = color.YellowString(failure.RuleName)
fName = color.YellowString("https://revive.run/r#" + failure.RuleName)
}
return []string{failure.GetFilename(), pos, fName, fString}
}

27
formatter/unix.go Normal file
View File

@ -0,0 +1,27 @@
package formatter
import (
"fmt"
"github.com/mgechev/revive/lint"
)
// Unix is an implementation of the Formatter interface
// which formats the errors to a simple line based error format
// main.go:24:9: [errorf] should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...)
type Unix struct {
Metadata lint.FormatterMetadata
}
// Name returns the name of the formatter
func (f *Unix) Name() string {
return "unix"
}
// Format formats the failures gotten from the lint.
func (f *Unix) Format(failures <-chan lint.Failure, _ lint.RulesConfig) (string, error) {
for failure := range failures {
fmt.Printf("%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure)
}
return "", nil
}

15
go.mod Normal file
View File

@ -0,0 +1,15 @@
module github.com/mgechev/revive
require (
github.com/BurntSushi/toml v0.3.0
github.com/fatih/color v1.7.0
github.com/fatih/structtag v1.0.0
github.com/mattn/go-colorable v0.0.9 // indirect
github.com/mattn/go-isatty v0.0.4 // indirect
github.com/mattn/go-runewidth v0.0.3 // indirect
github.com/mgechev/dots v0.0.0-20180605013149-8e09d8ea2757
github.com/olekukonko/tablewriter v0.0.0-20180912035003-be2c049b30cc
github.com/pkg/errors v0.8.0
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e // indirect
golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1
)

22
go.sum Normal file
View File

@ -0,0 +1,22 @@
github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structtag v1.0.0 h1:pTHj65+u3RKWYPSGaU290FpI/dXxTaHdVwVwbcPKmEc=
github.com/fatih/structtag v1.0.0/go.mod h1:IKitwq45uXL/yqi5mYghiD3w9H6eTOvI9vnk8tXMphA=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mgechev/dots v0.0.0-20180605013149-8e09d8ea2757 h1:KTwJ7Lo3KDKMknRYN5JEFRGIM4IkG59QjFFM2mxsMEU=
github.com/mgechev/dots v0.0.0-20180605013149-8e09d8ea2757/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg=
github.com/olekukonko/tablewriter v0.0.0-20180912035003-be2c049b30cc h1:rQ1O4ZLYR2xXHXgBCCfIIGnuZ0lidMQw2S5n1oOv+Wg=
github.com/olekukonko/tablewriter v0.0.0-20180912035003-be2c049b30cc/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1 h1:dzEuQYa6+a3gROnSlgly5ERUm4SZKJt+dh+4iSbO+bI=
golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -29,7 +29,6 @@ type Failure struct {
Position FailurePosition
Node ast.Node `json:"-"`
Confidence float64
URL string
// For future use
ReplacementLine string
}

View File

@ -56,6 +56,11 @@ func (f *File) Render(x interface{}) string {
return buf.String()
}
// CommentMap builds a comment map for the file.
func (f *File) CommentMap() ast.CommentMap {
return ast.NewCommentMap(f.Pkg.fset, f.AST, f.AST.Comments)
}
var basicTypeKinds = map[types.BasicKind]string{
types.UntypedBool: "bool",
types.UntypedInt: "int",

View File

@ -77,7 +77,9 @@ func (p *Package) TypeCheck() error {
anyFile = f
astFiles = append(astFiles, f.AST)
}
typesPkg, err := config.Check(anyFile.AST.Name.Name, p.fset, astFiles, info)
typesPkg, err := check(config, anyFile.AST.Name.Name, p.fset, astFiles, info)
// Remember the typechecking info, even if config.Check failed,
// since we will get partial information.
p.TypesPkg = typesPkg
@ -86,6 +88,20 @@ func (p *Package) TypeCheck() error {
return err
}
// check function encapsulates the call to go/types.Config.Check method and
// recovers if the called method panics (see issue #59)
func check(config *types.Config, n string, fset *token.FileSet, astFiles []*ast.File, info *types.Info) (p *types.Package, err error) {
defer func() {
if r := recover(); r != nil {
err, _ = r.(error)
p = nil
return
}
}()
return config.Check(n, fset, astFiles, info)
}
// TypeOf returns the type of an expression.
func (p *Package) TypeOf(expr ast.Expr) types.Type {
if p.TypesInfo == nil {

View File

@ -6,7 +6,7 @@ import (
)
// Name returns a different name if it should be different.
func Name(name string) (should string) {
func Name(name string, whitelist, blacklist []string) (should string) {
// Fast path for simple cases: "_" and all lowercase.
if name == "_" {
return name
@ -56,7 +56,17 @@ func Name(name string) (should string) {
// [w,i) is a word.
word := string(runes[w:i])
if u := strings.ToUpper(word); commonInitialisms[u] {
ignoreInitWarnings := map[string]bool{}
for _, i := range whitelist {
ignoreInitWarnings[i] = true
}
extraInits := map[string]bool{}
for _, i := range blacklist {
extraInits[i] = true
}
if u := strings.ToUpper(word); (commonInitialisms[u] || extraInits[u]) && !ignoreInitWarnings[u] {
// Keep consistent case, which is lowercase only at the start.
if w == 0 && unicode.IsLower(runes[w]) {
u = strings.ToLower(u)

151
rule/add-constant.go Normal file
View File

@ -0,0 +1,151 @@
package rule
import (
"fmt"
"github.com/mgechev/revive/lint"
"go/ast"
"strconv"
"strings"
)
const (
defaultStrLitLimit = 2
kindFLOAT = "FLOAT"
kindINT = "INT"
kindSTRING = "STRING"
)
type whiteList map[string]map[string]bool
func newWhiteList() whiteList {
return map[string]map[string]bool{kindINT: map[string]bool{}, kindFLOAT: map[string]bool{}, kindSTRING: map[string]bool{}}
}
func (wl whiteList) add(kind string, list string) {
elems := strings.Split(list, ",")
for _, e := range elems {
wl[kind][e] = true
}
}
// AddConstantRule lints unused params in functions.
type AddConstantRule struct{}
// Apply applies the rule to given file.
func (r *AddConstantRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
strLitLimit := defaultStrLitLimit
var whiteList = newWhiteList()
if len(arguments) > 0 {
args, ok := arguments[0].(map[string]interface{})
if !ok {
panic(fmt.Sprintf("Invalid argument to the add-constant rule. Expecting a k,v map, got %T", arguments[0]))
}
for k, v := range args {
kind := ""
switch k {
case "allowFloats":
kind = kindFLOAT
fallthrough
case "allowInts":
if kind == "" {
kind = kindINT
}
fallthrough
case "allowStrs":
if kind == "" {
kind = kindSTRING
}
list, ok := v.(string)
if !ok {
panic(fmt.Sprintf("Invalid argument to the add-constant rule, string expected. Got '%v' (%T)", v, v))
}
whiteList.add(kind, list)
case "maxLitCount":
sl, ok := v.(string)
if !ok {
panic(fmt.Sprintf("Invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v' (%T)", v, v))
}
limit, err := strconv.Atoi(sl)
if err != nil {
panic(fmt.Sprintf("Invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v'", v))
}
strLitLimit = limit
}
}
}
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintAddConstantRule{onFailure: onFailure, strLits: make(map[string]int, 0), strLitLimit: strLitLimit, whiteLst: whiteList}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (r *AddConstantRule) Name() string {
return "add-constant"
}
type lintAddConstantRule struct {
onFailure func(lint.Failure)
strLits map[string]int
strLitLimit int
whiteLst whiteList
}
func (w lintAddConstantRule) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.GenDecl:
return nil // skip declarations
case *ast.BasicLit:
switch kind := n.Kind.String(); kind {
case kindFLOAT, kindINT:
w.checkNumLit(kind, n)
case kindSTRING:
w.checkStrLit(n)
}
}
return w
}
func (w lintAddConstantRule) checkStrLit(n *ast.BasicLit) {
if w.whiteLst[kindSTRING][n.Value] {
return
}
count := w.strLits[n.Value]
if count >= 0 {
w.strLits[n.Value] = count + 1
if w.strLits[n.Value] > w.strLitLimit {
w.onFailure(lint.Failure{
Confidence: 1,
Node: n,
Category: "style",
Failure: fmt.Sprintf("string literal %s appears, at least, %d times, create a named constant for it", n.Value, w.strLits[n.Value]),
})
w.strLits[n.Value] = -1 // mark it to avoid failing again on the same literal
}
}
}
func (w lintAddConstantRule) checkNumLit(kind string, n *ast.BasicLit) {
if w.whiteLst[kind][n.Value] {
return
}
w.onFailure(lint.Failure{
Confidence: 1,
Node: n,
Category: "style",
Failure: fmt.Sprintf("avoid magic numbers like '%s', create a named constant for it", n.Value),
})
}

94
rule/atomic.go Normal file
View File

@ -0,0 +1,94 @@
package rule
import (
"go/ast"
"go/token"
"go/types"
"github.com/mgechev/revive/lint"
)
// AtomicRule lints given else constructs.
type AtomicRule struct{}
// Apply applies the rule to given file.
func (r *AtomicRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
walker := atomic{
pkgTypesInfo: file.Pkg.TypesInfo,
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
}
ast.Walk(walker, file.AST)
return failures
}
// Name returns the rule name.
func (r *AtomicRule) Name() string {
return "atomic"
}
type atomic struct {
pkgTypesInfo *types.Info
onFailure func(lint.Failure)
}
func (w atomic) Visit(node ast.Node) ast.Visitor {
n, ok := node.(*ast.AssignStmt)
if !ok {
return w
}
if len(n.Lhs) != len(n.Rhs) {
return nil // skip assignment sub-tree
}
if len(n.Lhs) == 1 && n.Tok == token.DEFINE {
return nil // skip assignment sub-tree
}
for i, right := range n.Rhs {
call, ok := right.(*ast.CallExpr)
if !ok {
continue
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
continue
}
pkgIdent, _ := sel.X.(*ast.Ident)
if w.pkgTypesInfo != nil {
pkgName, ok := w.pkgTypesInfo.Uses[pkgIdent].(*types.PkgName)
if !ok || pkgName.Imported().Path() != "sync/atomic" {
continue
}
}
switch sel.Sel.Name {
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
left := n.Lhs[i]
if len(call.Args) != 2 {
continue
}
arg := call.Args[0]
broken := false
if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND {
broken = gofmt(left) == gofmt(uarg.X)
} else if star, ok := left.(*ast.StarExpr); ok {
broken = gofmt(star.X) == gofmt(arg)
}
if broken {
w.onFailure(lint.Failure{
Confidence: 1,
Failure: "direct assignment to atomic value",
Node: n,
})
}
}
}
return w
}

View File

@ -10,7 +10,7 @@ import (
type BlankImportsRule struct{}
// Apply applies the rule to given file.
func (r *BlankImportsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *BlankImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST
@ -38,7 +38,7 @@ type lintBlankImports struct {
onFailure func(lint.Failure)
}
func (w lintBlankImports) Visit(n ast.Node) ast.Visitor {
func (w lintBlankImports) Visit(_ ast.Node) ast.Visitor {
// In package main and in tests, we don't complain about blank imports.
if w.file.Pkg.IsMain() || w.file.IsTest() {
return nil
@ -67,7 +67,6 @@ func (w lintBlankImports) Visit(n ast.Node) ast.Visitor {
Failure: "a blank import should be only in a main or test package, or have a comment justifying it",
Confidence: 1,
Category: "imports",
URL: "",
})
}
}

View File

@ -0,0 +1,73 @@
package rule
import (
"go/ast"
"go/token"
"github.com/mgechev/revive/lint"
)
// BoolLiteralRule warns when logic expressions contains Boolean literals.
type BoolLiteralRule struct{}
// Apply applies the rule to given file.
func (r *BoolLiteralRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
astFile := file.AST
w := &lintBoolLiteral{astFile, onFailure}
ast.Walk(w, astFile)
return failures
}
// Name returns the rule name.
func (r *BoolLiteralRule) Name() string {
return "bool-literal-in-expr"
}
type lintBoolLiteral struct {
file *ast.File
onFailure func(lint.Failure)
}
func (w *lintBoolLiteral) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.BinaryExpr:
if !isBoolOp(n.Op) {
return w
}
lexeme, ok := isExprABooleanLit(n.X)
if !ok {
lexeme, ok = isExprABooleanLit(n.Y)
if !ok {
return w
}
}
isConstant := (n.Op == token.LAND && lexeme == "false") || (n.Op == token.LOR && lexeme == "true")
if isConstant {
w.addFailure(n, "Boolean expression seems to always evaluate to "+lexeme, "logic")
} else {
w.addFailure(n, "omit Boolean literal in expression", "style")
}
}
return w
}
func (w lintBoolLiteral) addFailure(node ast.Node, msg string, cat string) {
w.onFailure(lint.Failure{
Confidence: 1,
Node: node,
Category: cat,
Failure: msg,
})
}

190
rule/confusing-naming.go Normal file
View File

@ -0,0 +1,190 @@
package rule
import (
"fmt"
"go/ast"
"strings"
"sync"
"github.com/mgechev/revive/lint"
)
type referenceMethod struct {
fileName string
id *ast.Ident
}
type pkgMethods struct {
pkg *lint.Package
methods map[string]map[string]*referenceMethod
mu *sync.Mutex
}
type packages struct {
pkgs []pkgMethods
mu sync.Mutex
}
func (ps *packages) methodNames(lp *lint.Package) pkgMethods {
ps.mu.Lock()
for _, pkg := range ps.pkgs {
if pkg.pkg == lp {
ps.mu.Unlock()
return pkg
}
}
pkgm := pkgMethods{pkg: lp, methods: make(map[string]map[string]*referenceMethod), mu: &sync.Mutex{}}
ps.pkgs = append(ps.pkgs, pkgm)
ps.mu.Unlock()
return pkgm
}
var allPkgs = packages{pkgs: make([]pkgMethods, 1)}
// ConfusingNamingRule lints method names that differ only by capitalization
type ConfusingNamingRule struct{}
// Apply applies the rule to given file.
func (r *ConfusingNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST
pkgm := allPkgs.methodNames(file.Pkg)
walker := lintConfusingNames{
fileName: file.Name,
pkgm: pkgm,
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
}
ast.Walk(&walker, fileAst)
return failures
}
// Name returns the rule name.
func (r *ConfusingNamingRule) Name() string {
return "confusing-naming"
}
//checkMethodName checks if a given method/function name is similar (just case differences) to other method/function of the same struct/file.
func checkMethodName(holder string, id *ast.Ident, w *lintConfusingNames) {
if id.Name == "init" && holder == defaultStructName {
// ignore init functions
return
}
pkgm := w.pkgm
name := strings.ToUpper(id.Name)
pkgm.mu.Lock()
defer pkgm.mu.Unlock()
if pkgm.methods[holder] != nil {
if pkgm.methods[holder][name] != nil {
refMethod := pkgm.methods[holder][name]
// confusing names
var kind string
if holder == defaultStructName {
kind = "function"
} else {
kind = "method"
}
var fileName string
if w.fileName == refMethod.fileName {
fileName = "the same source file"
} else {
fileName = refMethod.fileName
}
w.onFailure(lint.Failure{
Failure: fmt.Sprintf("Method '%s' differs only by capitalization to %s '%s' in %s", id.Name, kind, refMethod.id.Name, fileName),
Confidence: 1,
Node: id,
Category: "naming",
})
return
}
} else {
pkgm.methods[holder] = make(map[string]*referenceMethod, 1)
}
// update the black list
if pkgm.methods[holder] == nil {
println("no entry for '", holder, "'")
}
pkgm.methods[holder][name] = &referenceMethod{fileName: w.fileName, id: id}
}
type lintConfusingNames struct {
fileName string
pkgm pkgMethods
onFailure func(lint.Failure)
}
const defaultStructName = "_" // used to map functions
//getStructName of a function receiver. Defaults to defaultStructName
func getStructName(r *ast.FieldList) string {
result := defaultStructName
if r == nil || len(r.List) < 1 {
return result
}
t := r.List[0].Type
if p, _ := t.(*ast.StarExpr); p != nil { // if a pointer receiver => dereference pointer receiver types
t = p.X
}
if p, _ := t.(*ast.Ident); p != nil {
result = p.Name
}
return result
}
func checkStructFields(fields *ast.FieldList, structName string, w *lintConfusingNames) {
bl := make(map[string]bool, len(fields.List))
for _, f := range fields.List {
for _, id := range f.Names {
normName := strings.ToUpper(id.Name)
if bl[normName] {
w.onFailure(lint.Failure{
Failure: fmt.Sprintf("Field '%s' differs only by capitalization to other field in the struct type %s", id.Name, structName),
Confidence: 1,
Node: id,
Category: "naming",
})
} else {
bl[normName] = true
}
}
}
}
func (w *lintConfusingNames) Visit(n ast.Node) ast.Visitor {
switch v := n.(type) {
case *ast.FuncDecl:
// Exclude naming warnings for functions that are exported to C but
// not exported in the Go API.
// See https://github.com/golang/lint/issues/144.
if ast.IsExported(v.Name.Name) || !isCgoExported(v) {
checkMethodName(getStructName(v.Recv), v.Name, w)
}
case *ast.TypeSpec:
if s, ok := v.Type.(*ast.StructType); ok {
checkStructFields(s.Fields, v.Name.Name, w)
}
default:
// will add other checks like field names, struct names, etc.
}
return w
}

67
rule/confusing-results.go Normal file
View File

@ -0,0 +1,67 @@
package rule
import (
"go/ast"
"github.com/mgechev/revive/lint"
)
// ConfusingResultsRule lints given function declarations
type ConfusingResultsRule struct{}
// Apply applies the rule to given file.
func (r *ConfusingResultsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST
walker := lintConfusingResults{
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
}
ast.Walk(walker, fileAst)
return failures
}
// Name returns the rule name.
func (r *ConfusingResultsRule) Name() string {
return "confusing-results"
}
type lintConfusingResults struct {
onFailure func(lint.Failure)
}
func (w lintConfusingResults) Visit(n ast.Node) ast.Visitor {
fn, ok := n.(*ast.FuncDecl)
if !ok || fn.Type.Results == nil || len(fn.Type.Results.List) < 2 {
return w
}
lastType := ""
for _, result := range fn.Type.Results.List {
if len(result.Names) > 0 {
return w
}
t, ok := result.Type.(*ast.Ident)
if !ok {
return w
}
if t.Name == lastType {
w.onFailure(lint.Failure{
Node: n,
Confidence: 1,
Category: "naming",
Failure: "unnamed results of the same type may be confusing, consider using named results",
})
break
}
lastType = t.Name
}
return w
}

View File

@ -0,0 +1,88 @@
package rule
import (
"github.com/mgechev/revive/lint"
"go/ast"
"go/token"
)
// ConstantLogicalExprRule warns on constant logical expressions.
type ConstantLogicalExprRule struct{}
// Apply applies the rule to given file.
func (r *ConstantLogicalExprRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
astFile := file.AST
w := &lintConstantLogicalExpr{astFile, onFailure}
ast.Walk(w, astFile)
return failures
}
// Name returns the rule name.
func (r *ConstantLogicalExprRule) Name() string {
return "constant-logical-expr"
}
type lintConstantLogicalExpr struct {
file *ast.File
onFailure func(lint.Failure)
}
func (w *lintConstantLogicalExpr) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.BinaryExpr:
if !w.isOperatorWithLogicalResult(n.Op) {
return w
}
if gofmt(n.X) != gofmt(n.Y) { // check if subexpressions are the same
return w
}
if n.Op == token.EQL {
w.newFailure(n, "expression always evaluates to true")
return w
}
if w.isInequalityOperator(n.Op) {
w.newFailure(n, "expression always evaluates to false")
return w
}
w.newFailure(n, "left and right hand-side sub-expressions are the same")
}
return w
}
func (w *lintConstantLogicalExpr) isOperatorWithLogicalResult(t token.Token) bool {
switch t {
case token.LAND, token.LOR, token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ:
return true
}
return false
}
func (w *lintConstantLogicalExpr) isInequalityOperator(t token.Token) bool {
switch t {
case token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ:
return true
}
return false
}
func (w lintConstantLogicalExpr) newFailure(node ast.Node, msg string) {
w.onFailure(lint.Failure{
Confidence: 1,
Node: node,
Category: "logic",
Failure: msg,
})
}

View File

@ -10,7 +10,7 @@ import (
type ContextAsArgumentRule struct{}
// Apply applies the rule to given file.
func (r *ContextAsArgumentRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *ContextAsArgumentRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST
@ -50,7 +50,6 @@ func (w lintContextArguments) Visit(n ast.Node) ast.Visitor {
w.onFailure(lint.Failure{
Node: fn,
Category: "arg-order",
URL: "https://golang.org/pkg/context/",
Failure: "context.Context should be the first parameter of a function",
Confidence: 0.9,
})

View File

@ -12,7 +12,7 @@ import (
type ContextKeysType struct{}
// Apply applies the rule to given file.
func (r *ContextKeysType) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *ContextKeysType) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST

View File

@ -47,7 +47,7 @@ type lintCyclomatic struct {
onFailure func(lint.Failure)
}
func (w lintCyclomatic) Visit(n ast.Node) ast.Visitor {
func (w lintCyclomatic) Visit(_ ast.Node) ast.Visitor {
f := w.file
for _, decl := range f.AST.Decls {
if fn, ok := decl.(*ast.FuncDecl); ok {

View File

@ -11,7 +11,7 @@ import (
type DeepExitRule struct{}
// Apply applies the rule to given file.
func (r *DeepExitRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *DeepExitRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
@ -81,7 +81,6 @@ func (w lintDeepExit) Visit(node ast.Node) ast.Visitor {
Confidence: 1,
Node: ce,
Category: "bad practice",
URL: "#deep-exit",
Failure: fmt.Sprintf("calls to %s.%s only in main() or init() functions", pkg, fn),
})
}

View File

@ -10,7 +10,7 @@ import (
type DotImportsRule struct{}
// Apply applies the rule to given file.
func (r *DotImportsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *DotImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST
@ -38,7 +38,7 @@ type lintImports struct {
onFailure func(lint.Failure)
}
func (w lintImports) Visit(n ast.Node) ast.Visitor {
func (w lintImports) Visit(_ ast.Node) ast.Visitor {
for i, is := range w.fileAst.Imports {
_ = i
if is.Name != nil && is.Name.Name == "." && !w.file.IsTest() {
@ -47,7 +47,6 @@ func (w lintImports) Visit(n ast.Node) ast.Visitor {
Failure: "should not use dot imports",
Node: is,
Category: "imports",
URL: "#import-dot",
})
}
}

View File

@ -10,7 +10,7 @@ import (
type EmptyBlockRule struct{}
// Apply applies the rule to given file.
func (r *EmptyBlockRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *EmptyBlockRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
@ -59,7 +59,6 @@ func (w lintEmptyBlock) Visit(node ast.Node) ast.Visitor {
Confidence: 1,
Node: block,
Category: "logic",
URL: "#empty-block",
Failure: "this block is empty, you can remove it",
})
}

113
rule/empty-lines.go Normal file
View File

@ -0,0 +1,113 @@
package rule
import (
"go/ast"
"go/token"
"github.com/mgechev/revive/lint"
)
// EmptyLinesRule lints empty lines in blocks.
type EmptyLinesRule struct{}
// Apply applies the rule to given file.
func (r *EmptyLinesRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintEmptyLines{file, file.CommentMap(), onFailure}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (r *EmptyLinesRule) Name() string {
return "empty-lines"
}
type lintEmptyLines struct {
file *lint.File
cmap ast.CommentMap
onFailure func(lint.Failure)
}
func (w lintEmptyLines) Visit(node ast.Node) ast.Visitor {
block, ok := node.(*ast.BlockStmt)
if !ok {
return w
}
w.checkStart(block)
w.checkEnd(block)
return w
}
func (w lintEmptyLines) checkStart(block *ast.BlockStmt) {
if len(block.List) == 0 {
return
}
start := w.position(block.Lbrace)
firstNode := block.List[0]
if w.commentBetween(start, firstNode) {
return
}
first := w.position(firstNode.Pos())
if first.Line-start.Line > 1 {
w.onFailure(lint.Failure{
Confidence: 1,
Node: block,
Category: "style",
Failure: "extra empty line at the start of a block",
})
}
}
func (w lintEmptyLines) checkEnd(block *ast.BlockStmt) {
if len(block.List) < 1 {
return
}
end := w.position(block.Rbrace)
lastNode := block.List[len(block.List)-1]
if w.commentBetween(end, lastNode) {
return
}
last := w.position(lastNode.End())
if end.Line-last.Line > 1 {
w.onFailure(lint.Failure{
Confidence: 1,
Node: lastNode,
Category: "style",
Failure: "extra empty line at the end of a block",
})
}
}
func (w lintEmptyLines) commentBetween(position token.Position, node ast.Node) bool {
comments := w.cmap.Filter(node).Comments()
if len(comments) == 0 {
return false
}
for _, comment := range comments {
start, end := w.position(comment.Pos()), w.position(comment.End())
if start.Line-position.Line == 1 || position.Line-end.Line == 1 {
return true
}
}
return false
}
func (w lintEmptyLines) position(pos token.Pos) token.Position {
return w.file.ToPosition(pos)
}

View File

@ -13,7 +13,7 @@ import (
type ErrorNamingRule struct{}
// Apply applies the rule to given file.
func (r *ErrorNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *ErrorNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST
@ -41,7 +41,7 @@ type lintErrors struct {
onFailure func(lint.Failure)
}
func (w lintErrors) Visit(n ast.Node) ast.Visitor {
func (w lintErrors) Visit(_ ast.Node) ast.Visitor {
for _, decl := range w.fileAst.Decls {
gd, ok := decl.(*ast.GenDecl)
if !ok || gd.Tok != token.VAR {

View File

@ -10,7 +10,7 @@ import (
type ErrorReturnRule struct{}
// Apply applies the rule to given file.
func (r *ErrorReturnRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *ErrorReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST

View File

@ -14,7 +14,7 @@ import (
type ErrorStringsRule struct{}
// Apply applies the rule to given file.
func (r *ErrorStringsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *ErrorStringsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST
@ -69,7 +69,6 @@ func (w lintErrorStrings) Visit(n ast.Node) ast.Visitor {
w.onFailure(lint.Failure{
Node: str,
Confidence: conf,
URL: "#error-strings",
Category: "errors",
Failure: "error strings should not be capitalized or end with punctuation or a newline",
})

View File

@ -13,7 +13,7 @@ import (
type ErrorfRule struct{}
// Apply applies the rule to given file.
func (r *ErrorfRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *ErrorfRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST

View File

@ -15,7 +15,7 @@ import (
type ExportedRule struct{}
// Apply applies the rule to given file.
func (r *ExportedRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *ExportedRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
if isTest(file) {
@ -80,7 +80,6 @@ func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) {
w.onFailure(lint.Failure{
Node: fn,
Confidence: 1,
URL: "#doc-comments",
Category: "comments",
Failure: fmt.Sprintf("exported %s %s should have comment or be unexported", kind, name),
})
@ -92,7 +91,6 @@ func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) {
w.onFailure(lint.Failure{
Node: fn.Doc,
Confidence: 0.8,
URL: "#doc-comments",
Category: "comments",
Failure: fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix),
})
@ -123,7 +121,6 @@ func (w *lintExported) checkStutter(id *ast.Ident, thing string) {
w.onFailure(lint.Failure{
Node: id,
Confidence: 0.8,
URL: "#package-names",
Category: "naming",
Failure: fmt.Sprintf("%s name will be used as %s.%s by other packages, and that stutters; consider calling this %s", thing, pkg, name, rem),
})
@ -138,7 +135,6 @@ func (w *lintExported) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) {
w.onFailure(lint.Failure{
Node: t,
Confidence: 1,
URL: "#doc-comments",
Category: "comments",
Failure: fmt.Sprintf("exported type %v should have comment or be unexported", t.Name),
})
@ -146,8 +142,11 @@ func (w *lintExported) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) {
}
s := doc.Text()
articles := [...]string{"A", "An", "The"}
articles := [...]string{"A", "An", "The", "This"}
for _, a := range articles {
if t.Name.Name == a {
continue
}
if strings.HasPrefix(s, a+" ") {
s = s[len(a)+1:]
break
@ -157,7 +156,6 @@ func (w *lintExported) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) {
w.onFailure(lint.Failure{
Node: doc,
Confidence: 1,
URL: "#doc-comments",
Category: "comments",
Failure: fmt.Sprintf(`comment on exported type %v should be of the form "%v ..." (with optional leading article)`, t.Name, t.Name),
})
@ -202,7 +200,6 @@ func (w *lintExported) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genD
w.onFailure(lint.Failure{
Confidence: 1,
Node: vs,
URL: "#doc-comments",
Category: "comments",
Failure: fmt.Sprintf("exported %s %s should have comment%s or be unexported", kind, name, block),
})
@ -224,7 +221,6 @@ func (w *lintExported) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genD
w.onFailure(lint.Failure{
Confidence: 1,
Node: doc,
URL: "#doc-comments",
Category: "comments",
Failure: fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix),
})

View File

@ -51,7 +51,7 @@ type lintFileHeader struct {
onFailure func(lint.Failure)
}
func (w lintFileHeader) Visit(n ast.Node) ast.Visitor {
func (w lintFileHeader) Visit(_ ast.Node) ast.Visitor {
g := w.fileAst.Comments[0]
failure := lint.Failure{
Node: w.fileAst,

104
rule/flag-param.go Normal file
View File

@ -0,0 +1,104 @@
package rule
import (
"fmt"
"github.com/mgechev/revive/lint"
"go/ast"
)
// FlagParamRule lints given else constructs.
type FlagParamRule struct{}
// Apply applies the rule to given file.
func (r *FlagParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintFlagParamRule{onFailure: onFailure}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (r *FlagParamRule) Name() string {
return "flag-parameter"
}
type lintFlagParamRule struct {
onFailure func(lint.Failure)
}
func (w lintFlagParamRule) Visit(node ast.Node) ast.Visitor {
fd, ok := node.(*ast.FuncDecl)
if !ok {
return w
}
if fd.Body == nil {
return nil // skip whole function declaration
}
for _, p := range fd.Type.Params.List {
t := p.Type
id, ok := t.(*ast.Ident)
if !ok {
continue
}
if id.Name != "bool" {
continue
}
cv := conditionVisitor{p.Names, fd, w}
ast.Walk(cv, fd.Body)
}
return w
}
type conditionVisitor struct {
ids []*ast.Ident
fd *ast.FuncDecl
linter lintFlagParamRule
}
func (w conditionVisitor) Visit(node ast.Node) ast.Visitor {
ifStmt, ok := node.(*ast.IfStmt)
if !ok {
return w
}
fselect := func(n ast.Node) bool {
ident, ok := n.(*ast.Ident)
if !ok {
return false
}
for _, id := range w.ids {
if ident.Name == id.Name {
return true
}
}
return false
}
uses := pick(ifStmt.Cond, fselect, nil)
if len(uses) < 1 {
return w
}
w.linter.onFailure(lint.Failure{
Confidence: 1,
Node: w.fd.Type.Params,
Category: "bad practice",
Failure: fmt.Sprintf("parameter '%s' seems to be a control flag, avoid control coupling", uses[0]),
})
return nil
}

View File

@ -0,0 +1,68 @@
package rule
import (
"fmt"
"go/ast"
"github.com/mgechev/revive/lint"
)
// FunctionResultsLimitRule lints given else constructs.
type FunctionResultsLimitRule struct{}
// Apply applies the rule to given file.
func (r *FunctionResultsLimitRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
if len(arguments) != 1 {
panic(`invalid configuration for "function-result-limit"`)
}
max, ok := arguments[0].(int64) // Alt. non panicking version
if !ok {
panic(fmt.Sprintf(`invalid value passed as return results number to the "function-result-limit" rule; need int64 but got %T`, arguments[0]))
}
if max < 0 {
panic(`the value passed as return results number to the "function-result-limit" rule cannot be negative`)
}
var failures []lint.Failure
walker := lintFunctionResultsNum{
max: int(max),
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
}
ast.Walk(walker, file.AST)
return failures
}
// Name returns the rule name.
func (r *FunctionResultsLimitRule) Name() string {
return "function-result-limit"
}
type lintFunctionResultsNum struct {
max int
onFailure func(lint.Failure)
}
func (w lintFunctionResultsNum) Visit(n ast.Node) ast.Visitor {
node, ok := n.(*ast.FuncDecl)
if ok {
num := 0
if node.Type.Results != nil {
num = node.Type.Results.NumFields()
}
if num > w.max {
w.onFailure(lint.Failure{
Confidence: 1,
Failure: fmt.Sprintf("maximum number of return results per function exceeded; max %d but got %d", w.max, num),
Node: node.Type,
})
return w
}
}
return w
}

View File

@ -12,7 +12,7 @@ import (
type GetReturnRule struct{}
// Apply applies the rule to given file.
func (r *GetReturnRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *GetReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
@ -62,7 +62,6 @@ func (w lintReturnRule) Visit(node ast.Node) ast.Visitor {
Confidence: 0.8,
Node: fd,
Category: "logic",
URL: "#get-return",
Failure: fmt.Sprintf("function '%s' seems to be a getter but it does not return any result", fd.Name.Name),
})
}

View File

@ -12,7 +12,7 @@ import (
type IfReturnRule struct{}
// Apply applies the rule to given file.
func (r *IfReturnRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *IfReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {

69
rule/imports-blacklist.go Normal file
View File

@ -0,0 +1,69 @@
package rule
import (
"fmt"
"go/ast"
"github.com/mgechev/revive/lint"
)
// ImportsBlacklistRule lints given else constructs.
type ImportsBlacklistRule struct{}
// Apply applies the rule to given file.
func (r *ImportsBlacklistRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
var failures []lint.Failure
blacklist := make(map[string]bool, len(arguments))
for _, arg := range arguments {
argStr, ok := arg.(string)
if !ok {
panic(fmt.Sprintf("Invalid argument to the imports-blacklist rule. Expecting a string, got %T", arg))
}
// we add quotes if nt present, because when parsed, the value of the AST node, will be quoted
if len(argStr) > 2 && argStr[0] != '"' && argStr[len(argStr)-1] != '"' {
argStr = fmt.Sprintf(`"%s"`, argStr)
}
blacklist[argStr] = true
}
fileAst := file.AST
walker := blacklistedImports{
file: file,
fileAst: fileAst,
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
blacklist: blacklist,
}
ast.Walk(walker, fileAst)
return failures
}
// Name returns the rule name.
func (r *ImportsBlacklistRule) Name() string {
return "imports-blacklist"
}
type blacklistedImports struct {
file *lint.File
fileAst *ast.File
onFailure func(lint.Failure)
blacklist map[string]bool
}
func (w blacklistedImports) Visit(_ ast.Node) ast.Visitor {
for _, is := range w.fileAst.Imports {
if is.Path != nil && !w.file.IsTest() && w.blacklist[is.Path.Value] {
w.onFailure(lint.Failure{
Confidence: 1,
Failure: fmt.Sprintf("should not use the following blacklisted import: %s", is.Path.Value),
Node: is,
Category: "imports",
})
}
}
return nil
}

View File

@ -12,7 +12,7 @@ import (
type IncrementDecrementRule struct{}
// Apply applies the rule to given file.
func (r *IncrementDecrementRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *IncrementDecrementRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST

View File

@ -11,7 +11,7 @@ import (
type IndentErrorFlowRule struct{}
// Apply applies the rule to given file.
func (r *IndentErrorFlowRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *IndentErrorFlowRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
@ -71,10 +71,8 @@ func (w lintElse) Visit(node ast.Node) ast.Visitor {
Confidence: 1,
Node: ifStmt.Else,
Category: "indent",
URL: "#indent-error-flow",
Failure: "if block ends with a return statement, so drop this else and outdent its block" + extra,
})
}
return w
}

84
rule/line-length-limit.go Normal file
View File

@ -0,0 +1,84 @@
package rule
import (
"bufio"
"bytes"
"fmt"
"go/token"
"strings"
"unicode/utf8"
"github.com/mgechev/revive/lint"
)
// LineLengthLimitRule lints given else constructs.
type LineLengthLimitRule struct{}
// Apply applies the rule to given file.
func (r *LineLengthLimitRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
if len(arguments) != 1 {
panic(`invalid configuration for "line-length-limit"`)
}
max, ok := arguments[0].(int64) // Alt. non panicking version
if !ok || max < 0 {
panic(`invalid value passed as argument number to the "line-length-limit" rule`)
}
var failures []lint.Failure
checker := lintLineLengthNum{
max: int(max),
file: file,
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
}
checker.check()
return failures
}
// Name returns the rule name.
func (r *LineLengthLimitRule) Name() string {
return "line-length-limit"
}
type lintLineLengthNum struct {
max int
file *lint.File
onFailure func(lint.Failure)
}
func (r lintLineLengthNum) check() {
f := bytes.NewReader(r.file.Content())
spaces := strings.Repeat(" ", 4) // tab width = 4
l := 1
s := bufio.NewScanner(f)
for s.Scan() {
t := s.Text()
t = strings.Replace(t, "\t", spaces, -1)
c := utf8.RuneCountInString(t)
if c > r.max {
r.onFailure(lint.Failure{
Category: "code-style",
Position: lint.FailurePosition{
// Offset not set; it is non-trivial, and doesn't appear to be needed.
Start: token.Position{
Filename: r.file.Name,
Line: l,
Column: 0,
},
End: token.Position{
Filename: r.file.Name,
Line: l,
Column: c,
},
},
Confidence: 1,
Failure: fmt.Sprintf("line is %d characters, out of limit %d", c, r.max),
})
}
l++
}
}

View File

@ -1,7 +1,6 @@
package rule
import (
"fmt"
"go/ast"
"strings"
@ -31,7 +30,6 @@ func (r *MaxPublicStructsRule) Apply(file *lint.File, arguments lint.Arguments)
panic(`invalid value passed as argument number to the "max-public-structs" rule`)
}
fmt.Println("Name:", max, walker.current)
if walker.current > max {
walker.onFailure(lint.Failure{
Failure: "you have exceeded the maximum number of public struct declarations",

View File

@ -11,7 +11,7 @@ import (
type ModifiesParamRule struct{}
// Apply applies the rule to given file.
func (r *ModifiesParamRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *ModifiesParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
@ -43,23 +43,6 @@ func retrieveParamNames(pl []*ast.Field) map[string]bool {
return result
}
func checkAssign(lhs []ast.Expr, w lintModifiesParamRule) {
for _, e := range lhs {
id, ok := e.(*ast.Ident)
if ok {
if w.params[id.Name] {
w.onFailure(lint.Failure{
Confidence: 0.5,
Node: id,
Category: "bad practice",
URL: "#modifies-parameter",
Failure: fmt.Sprintf("parameter '%s' seems to be a modified", id.Name),
})
}
}
}
}
func (w lintModifiesParamRule) Visit(node ast.Node) ast.Visitor {
switch v := node.(type) {
case *ast.FuncDecl:
@ -87,7 +70,6 @@ func checkParam(id *ast.Ident, w *lintModifiesParamRule) {
Confidence: 0.5, // confidence is low because of shadow variables
Node: id,
Category: "bad practice",
URL: "#modifies-parameter",
Failure: fmt.Sprintf("parameter '%s' seems to be modified", id),
})
}

View File

@ -0,0 +1,134 @@
package rule
import (
"go/ast"
"strings"
"github.com/mgechev/revive/lint"
)
// ModifiesValRecRule lints assignments to value method-receivers.
type ModifiesValRecRule struct{}
// Apply applies the rule to given file.
func (r *ModifiesValRecRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintModifiesValRecRule{file: file, onFailure: onFailure}
file.Pkg.TypeCheck()
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (r *ModifiesValRecRule) Name() string {
return "modifies-value-receiver"
}
type lintModifiesValRecRule struct {
file *lint.File
onFailure func(lint.Failure)
}
func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.FuncDecl:
if n.Recv == nil {
return nil // skip, not a method
}
receiver := n.Recv.List[0]
if _, ok := receiver.Type.(*ast.StarExpr); ok {
return nil // skip, method with pointer receiver
}
if w.skipType(receiver.Type) {
return nil // skip, receiver is a map or array
}
if len(receiver.Names) < 1 {
return nil // skip, anonymous receiver
}
receiverName := receiver.Names[0].Name
if receiverName == "_" {
return nil // skip, anonymous receiver
}
fselect := func(n ast.Node) bool {
// look for assignments with the receiver in the right hand
asgmt, ok := n.(*ast.AssignStmt)
if !ok {
return false
}
for _, exp := range asgmt.Lhs {
switch e := exp.(type) {
case *ast.IndexExpr: // receiver...[] = ...
continue
case *ast.StarExpr: // *receiver = ...
continue
case *ast.SelectorExpr: // receiver.field = ...
name := w.getNameFromExpr(e.X)
if name == "" || name != receiverName {
continue
}
if w.skipType(ast.Expr(e.Sel)) {
continue
}
case *ast.Ident: // receiver := ...
if e.Name != receiverName {
continue
}
default:
continue
}
return true
}
return false
}
assignmentsToReceiver := pick(n.Body, fselect, nil)
for _, assignment := range assignmentsToReceiver {
w.onFailure(lint.Failure{
Node: assignment,
Confidence: 1,
Failure: "suspicious assignment to a by-value method receiver",
})
}
}
return w
}
func (w lintModifiesValRecRule) skipType(t ast.Expr) bool {
rt := w.file.Pkg.TypeOf(t)
if rt == nil {
return false
}
rt = rt.Underlying()
rtName := rt.String()
// skip when receiver is a map or array
return strings.HasPrefix(rtName, "[]") || strings.HasPrefix(rtName, "map[")
}
func (lintModifiesValRecRule) getNameFromExpr(ie ast.Expr) string {
ident, ok := ie.(*ast.Ident)
if !ok {
return ""
}
return ident.Name
}

View File

@ -17,7 +17,7 @@ import (
type PackageCommentsRule struct{}
// Apply applies the rule to given file.
func (r *PackageCommentsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *PackageCommentsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
if isTest(file) {
@ -45,7 +45,7 @@ type lintPackageComments struct {
onFailure func(lint.Failure)
}
func (l *lintPackageComments) Visit(n ast.Node) ast.Visitor {
func (l *lintPackageComments) Visit(_ ast.Node) ast.Visitor {
if l.file.IsTest() {
return nil
}
@ -84,7 +84,6 @@ func (l *lintPackageComments) Visit(n ast.Node) ast.Visitor {
},
Confidence: 0.9,
Failure: "package comment is detached; there should be no blank lines between it and the package statement",
URL: ref,
})
return nil
}
@ -96,7 +95,6 @@ func (l *lintPackageComments) Visit(n ast.Node) ast.Visitor {
Node: l.fileAst,
Confidence: 0.2,
Failure: "should have a package comment, unless it's in another file for this package",
URL: ref,
})
return nil
}
@ -107,7 +105,6 @@ func (l *lintPackageComments) Visit(n ast.Node) ast.Visitor {
Node: l.fileAst.Doc,
Confidence: 1,
Failure: "package comment should not have leading space",
URL: ref,
})
s = ts
}
@ -118,7 +115,6 @@ func (l *lintPackageComments) Visit(n ast.Node) ast.Visitor {
Node: l.fileAst.Doc,
Confidence: 1,
Failure: fmt.Sprintf(`package comment should be of the form "%s..."`, prefix),
URL: ref,
})
}
return nil

View File

@ -0,0 +1,111 @@
package rule
import (
"fmt"
"go/ast"
"github.com/mgechev/revive/lint"
)
// RangeValInClosureRule lints given else constructs.
type RangeValInClosureRule struct{}
// Apply applies the rule to given file.
func (r *RangeValInClosureRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
walker := rangeValInClosure{
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
}
ast.Walk(walker, file.AST)
return failures
}
// Name returns the rule name.
func (r *RangeValInClosureRule) Name() string {
return "range-val-in-closure"
}
type rangeValInClosure struct {
onFailure func(lint.Failure)
}
func (w rangeValInClosure) Visit(node ast.Node) ast.Visitor {
// Find the variables updated by the loop statement.
var vars []*ast.Ident
addVar := func(expr ast.Expr) {
if id, ok := expr.(*ast.Ident); ok {
vars = append(vars, id)
}
}
var body *ast.BlockStmt
switch n := node.(type) {
case *ast.RangeStmt:
body = n.Body
addVar(n.Key)
addVar(n.Value)
case *ast.ForStmt:
body = n.Body
switch post := n.Post.(type) {
case *ast.AssignStmt:
// e.g. for p = head; p != nil; p = p.next
for _, lhs := range post.Lhs {
addVar(lhs)
}
case *ast.IncDecStmt:
// e.g. for i := 0; i < n; i++
addVar(post.X)
}
}
if vars == nil {
return w
}
// Inspect a go or defer statement
// if it's the last one in the loop body.
// (We give up if there are following statements,
// because it's hard to prove go isn't followed by wait,
// or defer by return.)
if len(body.List) == 0 {
return w
}
var last *ast.CallExpr
switch s := body.List[len(body.List)-1].(type) {
case *ast.GoStmt:
last = s.Call
case *ast.DeferStmt:
last = s.Call
default:
return w
}
lit, ok := last.Fun.(*ast.FuncLit)
if !ok {
return w
}
if lit.Type == nil {
// Not referring to a variable (e.g. struct field name)
return w
}
ast.Inspect(lit.Body, func(n ast.Node) bool {
id, ok := n.(*ast.Ident)
if !ok || id.Obj == nil {
return true
}
for _, v := range vars {
if v.Obj == id.Obj {
w.onFailure(lint.Failure{
Confidence: 1,
Failure: fmt.Sprintf("loop variable %v captured by func literal", id.Name),
Node: n,
})
}
}
return true
})
return w
}

View File

@ -12,7 +12,7 @@ import (
type RangeRule struct{}
// Apply applies the rule to given file.
func (r *RangeRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *RangeRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {

View File

@ -11,7 +11,7 @@ import (
type ReceiverNamingRule struct{}
// Apply applies the rule to given file.
func (r *ReceiverNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *ReceiverNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST
@ -52,7 +52,6 @@ func (w lintReceiverName) Visit(n ast.Node) ast.Visitor {
w.onFailure(lint.Failure{
Node: n,
Confidence: 1,
URL: ref,
Category: "naming",
Failure: "receiver name should not be an underscore, omit the name if it is unused",
})
@ -62,7 +61,6 @@ func (w lintReceiverName) Visit(n ast.Node) ast.Visitor {
w.onFailure(lint.Failure{
Node: n,
Confidence: 1,
URL: ref,
Category: "naming",
Failure: `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`,
})
@ -73,7 +71,6 @@ func (w lintReceiverName) Visit(n ast.Node) ast.Visitor {
w.onFailure(lint.Failure{
Node: n,
Confidence: 1,
URL: ref,
Category: "naming",
Failure: fmt.Sprintf("receiver name %s should be consistent with previous receiver name %s for %s", name, prev, recv),
})

View File

@ -0,0 +1,145 @@
package rule
import (
"fmt"
"github.com/mgechev/revive/lint"
"go/ast"
"go/token"
)
// RedefinesBuiltinIDRule warns when a builtin identifier is shadowed.
type RedefinesBuiltinIDRule struct{}
// Apply applies the rule to given file.
func (r *RedefinesBuiltinIDRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
var builtInConstAndVars = map[string]bool{
"true": true,
"false": true,
"iota": true,
"nil": true,
}
var builtFunctions = map[string]bool{
"append": true,
"cap": true,
"close": true,
"complex": true,
"copy": true,
"delete": true,
"imag": true,
"len": true,
"make": true,
"new": true,
"panic": true,
"print": true,
"println": true,
"real": true,
"recover": true,
}
var builtInTypes = map[string]bool{
"ComplexType": true,
"FloatType": true,
"IntegerType": true,
"Type": true,
"Type1": true,
"bool": true,
"byte": true,
"complex128": true,
"complex64": true,
"error": true,
"float32": true,
"float64": true,
"int": true,
"int16": true,
"int32": true,
"int64": true,
"int8": true,
"rune": true,
"string": true,
"uint": true,
"uint16": true,
"uint32": true,
"uint64": true,
"uint8": true,
"uintptr": true,
}
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
astFile := file.AST
w := &lintRedefinesBuiltinID{builtInConstAndVars, builtFunctions, builtInTypes, onFailure}
ast.Walk(w, astFile)
return failures
}
// Name returns the rule name.
func (r *RedefinesBuiltinIDRule) Name() string {
return "redefines-builtin-id"
}
type lintRedefinesBuiltinID struct {
constsAndVars map[string]bool
funcs map[string]bool
types map[string]bool
onFailure func(lint.Failure)
}
func (w *lintRedefinesBuiltinID) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.GenDecl:
if n.Tok != token.TYPE {
return nil // skip if not type declaration
}
typeSpec, ok := n.Specs[0].(*ast.TypeSpec)
if !ok {
return nil
}
id := typeSpec.Name.Name
if w.types[id] {
w.addFailure(n, fmt.Sprintf("redefinition of the built-in type %s", id))
}
case *ast.FuncDecl:
if n.Recv != nil {
return w // skip methods
}
id := n.Name.Name
if w.funcs[id] {
w.addFailure(n, fmt.Sprintf("redefinition of the built-in function %s", id))
}
case *ast.AssignStmt:
for _, e := range n.Lhs {
id, ok := e.(*ast.Ident)
if !ok {
continue
}
if w.constsAndVars[id.Name] {
var msg string
if n.Tok == token.DEFINE {
msg = fmt.Sprintf("assignment creates a shadow of built-in identifier %s", id.Name)
} else {
msg = fmt.Sprintf("assignment modifies built-in identifier %s", id.Name)
}
w.addFailure(n, msg)
}
}
}
return w
}
func (w lintRedefinesBuiltinID) addFailure(node ast.Node, msg string) {
w.onFailure(lint.Failure{
Confidence: 1,
Node: node,
Category: "logic",
Failure: msg,
})
}

226
rule/struct-tag.go Normal file
View File

@ -0,0 +1,226 @@
package rule
import (
"fmt"
"github.com/fatih/structtag"
"github.com/mgechev/revive/lint"
"go/ast"
"strconv"
"strings"
)
// StructTagRule lints struct tags.
type StructTagRule struct{}
// Apply applies the rule to given file.
func (r *StructTagRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintStructTagRule{onFailure: onFailure}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (r *StructTagRule) Name() string {
return "struct-tag"
}
type lintStructTagRule struct {
onFailure func(lint.Failure)
usedTagNbr map[string]bool // list of used tag numbers
}
func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.StructType:
if n.Fields == nil || n.Fields.NumFields() < 1 {
return nil // skip empty structs
}
w.usedTagNbr = map[string]bool{} // init
for _, f := range n.Fields.List {
if f.Tag != nil {
w.checkTaggedField(f)
}
}
}
return w
}
// checkTaggedField checks the tag of the given field.
// precondition: the field has a tag
func (w lintStructTagRule) checkTaggedField(f *ast.Field) {
tags, err := structtag.Parse(strings.Trim(f.Tag.Value, "`"))
if err != nil || tags == nil {
w.addFailure(f.Tag, "malformed tag")
return
}
for _, tag := range tags.Tags() {
switch key := tag.Key; key {
case "asn1":
msg, ok := w.checkASN1Tag(f.Type, tag)
if !ok {
w.addFailure(f.Tag, msg)
}
case "bson":
msg, ok := w.checkBSONTag(tag.Options)
if !ok {
w.addFailure(f.Tag, msg)
}
case "default":
if !w.typeValueMatch(f.Type, tag.Name) {
w.addFailure(f.Tag, "field's type and default value's type mismatch")
}
case "json":
msg, ok := w.checkJSONTag(tag.Options)
if !ok {
w.addFailure(f.Tag, msg)
}
case "protobuf":
// Not implemented yet
case "required":
if tag.Name != "true" && tag.Name != "false" {
w.addFailure(f.Tag, "required should be 'true' or 'false'")
}
case "xml":
msg, ok := w.checkXMLTag(tag.Options)
if !ok {
w.addFailure(f.Tag, msg)
}
case "yaml":
msg, ok := w.checkYAMLTag(tag.Options)
if !ok {
w.addFailure(f.Tag, msg)
}
default:
// unknown key
}
}
}
func (w lintStructTagRule) checkASN1Tag(t ast.Expr, tag *structtag.Tag) (string, bool) {
checkList := append(tag.Options, tag.Name)
for _, opt := range checkList {
switch opt {
case "application", "explicit", "generalized", "ia5", "omitempty", "optional", "set", "utf8":
default:
if strings.HasPrefix(opt, "tag:") {
parts := strings.Split(opt, ":")
tagNumber := parts[1]
if w.usedTagNbr[tagNumber] {
return fmt.Sprintf("duplicated tag number %s", tagNumber), false
}
w.usedTagNbr[tagNumber] = true
continue
}
if strings.HasPrefix(opt, "default:") {
parts := strings.Split(opt, ":")
if len(parts) < 2 {
return "malformed default for ASN1 tag", false
}
if !w.typeValueMatch(t, parts[1]) {
return "field's type and default value's type mismatch", false
}
continue
}
return fmt.Sprintf("unknown option '%s' in ASN1 tag", opt), false
}
}
return "", true
}
func (w lintStructTagRule) checkBSONTag(options []string) (string, bool) {
for _, opt := range options {
switch opt {
case "inline", "minsize", "omitempty":
default:
return fmt.Sprintf("unknown option '%s' in BSON tag", opt), false
}
}
return "", true
}
func (w lintStructTagRule) checkJSONTag(options []string) (string, bool) {
for _, opt := range options {
switch opt {
case "omitempty", "string":
default:
return fmt.Sprintf("unknown option '%s' in JSON tag", opt), false
}
}
return "", true
}
func (w lintStructTagRule) checkXMLTag(options []string) (string, bool) {
for _, opt := range options {
switch opt {
case "any", "attr", "cdata", "chardata", "comment", "innerxml", "omitempty", "typeattr":
default:
return fmt.Sprintf("unknown option '%s' in XML tag", opt), false
}
}
return "", true
}
func (w lintStructTagRule) checkYAMLTag(options []string) (string, bool) {
for _, opt := range options {
switch opt {
case "flow", "inline", "omitempty":
default:
return fmt.Sprintf("unknown option '%s' in YAML tag", opt), false
}
}
return "", true
}
func (w lintStructTagRule) typeValueMatch(t ast.Expr, val string) bool {
tID, ok := t.(*ast.Ident)
if !ok {
return true
}
typeMatches := true
switch tID.Name {
case "bool":
typeMatches = val == "true" || val == "false"
case "float64":
_, err := strconv.ParseFloat(val, 64)
typeMatches = err == nil
case "int":
_, err := strconv.ParseInt(val, 10, 64)
typeMatches = err == nil
case "string":
case "nil":
default:
// unchecked type
}
return typeMatches
}
func (w lintStructTagRule) addFailure(n ast.Node, msg string) {
w.onFailure(lint.Failure{
Node: n,
Failure: msg,
Confidence: 1,
})
}

View File

@ -12,7 +12,7 @@ import (
type SuperfluousElseRule struct{}
// Apply applies the rule to given file.
func (r *SuperfluousElseRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *SuperfluousElseRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
@ -109,7 +109,6 @@ func newFailure(node ast.Node, msg string) lint.Failure {
Confidence: 1,
Node: node,
Category: "indent",
URL: "#indent-error-flow",
Failure: msg,
}
}

View File

@ -13,7 +13,7 @@ import (
type TimeNamingRule struct{}
// Apply applies the rule to given file.
func (r *TimeNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *TimeNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
@ -50,7 +50,7 @@ func (w *lintTimeNames) Visit(node ast.Node) ast.Visitor {
if pt, ok := typ.(*types.Pointer); ok {
typ = pt.Elem()
}
if !isNamedType(w.file.Pkg, typ, "time", "Duration") {
if !isNamedType(typ, "time", "Duration") {
continue
}
suffix := ""
@ -83,7 +83,7 @@ var timeSuffixes = []string{
"MS", "Ms",
}
func isNamedType(p *lint.Package, typ types.Type, importPath, name string) bool {
func isNamedType(typ types.Type, importPath, name string) bool {
n, ok := typ.(*types.Named)
if !ok {
return false

View File

@ -12,7 +12,7 @@ import (
type UnexportedReturnRule struct{}
// Apply applies the rule to given file.
func (r *UnexportedReturnRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
func (r *UnexportedReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
fileAst := file.AST

107
rule/unnecessary-stmt.go Normal file
View File

@ -0,0 +1,107 @@
package rule
import (
"go/ast"
"go/token"
"github.com/mgechev/revive/lint"
)
// UnnecessaryStmtRule warns on unnecessary statements.
type UnnecessaryStmtRule struct{}
// Apply applies the rule to given file.
func (r *UnnecessaryStmtRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintUnnecessaryStmtRule{onFailure}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (r *UnnecessaryStmtRule) Name() string {
return "unnecessary-stmt"
}
type lintUnnecessaryStmtRule struct {
onFailure func(lint.Failure)
}
func (w lintUnnecessaryStmtRule) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.FuncDecl:
if n.Body == nil || n.Type.Results != nil {
return w
}
stmts := n.Body.List
if len(stmts) == 0 {
return w
}
lastStmt := stmts[len(stmts)-1]
rs, ok := lastStmt.(*ast.ReturnStmt)
if !ok {
return w
}
if len(rs.Results) == 0 {
w.newFailure(lastStmt, "omit unnecessary return statement")
}
case *ast.SwitchStmt:
w.checkSwitchBody(n.Body)
case *ast.TypeSwitchStmt:
w.checkSwitchBody(n.Body)
case *ast.CaseClause:
if n.Body == nil {
return w
}
stmts := n.Body
if len(stmts) == 0 {
return w
}
lastStmt := stmts[len(stmts)-1]
rs, ok := lastStmt.(*ast.BranchStmt)
if !ok {
return w
}
if rs.Tok == token.BREAK && rs.Label == nil {
w.newFailure(lastStmt, "omit unnecessary break at the end of case clause")
}
}
return w
}
func (w lintUnnecessaryStmtRule) checkSwitchBody(b *ast.BlockStmt) {
cases := b.List
if len(cases) != 1 {
return
}
cc, ok := cases[0].(*ast.CaseClause)
if !ok {
return
}
if len(cc.List) > 1 { // skip cases with multiple expressions
return
}
w.newFailure(b, "switch with only one case can be replaced by an if-then")
}
func (w lintUnnecessaryStmtRule) newFailure(node ast.Node, msg string) {
w.onFailure(lint.Failure{
Confidence: 1,
Node: node,
Category: "style",
Failure: msg,
})
}

114
rule/unreachable-code.go Normal file
View File

@ -0,0 +1,114 @@
package rule
import (
"go/ast"
"github.com/mgechev/revive/lint"
)
// UnreachableCodeRule lints unreachable code.
type UnreachableCodeRule struct{}
// Apply applies the rule to given file.
func (r *UnreachableCodeRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
var branchingFunctions = map[string]map[string]bool{
"os": map[string]bool{"Exit": true},
"log": map[string]bool{
"Fatal": true,
"Fatalf": true,
"Fatalln": true,
"Panic": true,
"Panicf": true,
"Panicln": true,
},
}
w := lintUnreachableCode{onFailure, branchingFunctions}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (r *UnreachableCodeRule) Name() string {
return "unreachable-code"
}
type lintUnreachableCode struct {
onFailure func(lint.Failure)
branchingFunctions map[string]map[string]bool
}
func (w lintUnreachableCode) Visit(node ast.Node) ast.Visitor {
blk, ok := node.(*ast.BlockStmt)
if !ok {
return w
}
if len(blk.List) < 2 {
return w
}
loop:
for i, stmt := range blk.List[:len(blk.List)-1] {
// println("iterating ", len(blk.List))
next := blk.List[i+1]
if _, ok := next.(*ast.LabeledStmt); ok {
continue // skip if next statement is labeled
}
switch s := stmt.(type) {
case *ast.ReturnStmt:
w.onFailure(newUnreachableCodeFailure(s))
break loop
case *ast.BranchStmt:
token := s.Tok.String()
if token != "fallthrough" {
w.onFailure(newUnreachableCodeFailure(s))
break loop
}
case *ast.ExprStmt:
ce, ok := s.X.(*ast.CallExpr)
if !ok {
continue
}
// it's a function call
fc, ok := ce.Fun.(*ast.SelectorExpr)
if !ok {
continue
}
id, ok := fc.X.(*ast.Ident)
if !ok {
continue
}
fn := fc.Sel.Name
pkg := id.Name
if !w.branchingFunctions[pkg][fn] { // it isn't a call to a branching function
continue
}
if _, ok := next.(*ast.ReturnStmt); ok { // return statement needed to satisfy function signature
continue
}
w.onFailure(newUnreachableCodeFailure(s))
break loop
}
}
return w
}
func newUnreachableCodeFailure(node ast.Node) lint.Failure {
return lint.Failure{
Confidence: 1,
Node: node,
Category: "logic",
Failure: "unreachable code after this statement",
}
}

242
rule/unused-param.go Normal file
View File

@ -0,0 +1,242 @@
package rule
import (
"fmt"
"go/ast"
"go/token"
"github.com/mgechev/revive/lint"
)
// UnusedParamRule lints unused params in functions.
type UnusedParamRule struct{}
// Apply applies the rule to given file.
func (r *UnusedParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintUnusedParamRule{onFailure: onFailure}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (r *UnusedParamRule) Name() string {
return "unused-parameter"
}
type lintUnusedParamRule struct {
onFailure func(lint.Failure)
}
func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.FuncDecl:
fv := newFuncVisitor(retrieveNamedParams(n.Type.Params.List))
if n.Body != nil {
ast.Walk(fv, n.Body)
checkUnusedParams(w, fv.params, n)
}
return nil
}
return w
}
type scope struct {
vars map[string]bool
}
func newScope() scope {
return scope{make(map[string]bool, 0)}
}
func (s *scope) addVars(exps []ast.Expr) {
for _, e := range exps {
if id, ok := e.(*ast.Ident); ok {
s.vars[id.Name] = true
}
}
}
type scopeStack struct {
stk []scope
}
func (s *scopeStack) openScope() {
s.stk = append(s.stk, newScope())
}
func (s *scopeStack) closeScope() {
if len(s.stk) > 0 {
s.stk = s.stk[:len(s.stk)-1]
}
}
func (s *scopeStack) currentScope() scope {
if len(s.stk) > 0 {
return s.stk[len(s.stk)-1]
}
panic("no current scope")
}
func newScopeStack() scopeStack {
return scopeStack{make([]scope, 0)}
}
type funcVisitor struct {
sStk scopeStack
params map[string]bool
}
func newFuncVisitor(params map[string]bool) funcVisitor {
return funcVisitor{sStk: newScopeStack(), params: params}
}
func walkStmtList(v ast.Visitor, list []ast.Stmt) {
for _, s := range list {
ast.Walk(v, s)
}
}
func (v funcVisitor) Visit(node ast.Node) ast.Visitor {
varSelector := func(n ast.Node) bool {
id, ok := n.(*ast.Ident)
return ok && id.Obj != nil && id.Obj.Kind.String() == "var"
}
switch n := node.(type) {
case *ast.BlockStmt:
v.sStk.openScope()
walkStmtList(v, n.List)
v.sStk.closeScope()
return nil
case *ast.AssignStmt:
var uses []ast.Node
if isOpAssign(n.Tok) { // Case of id += expr
uses = append(uses, pickFromExpList(n.Lhs, varSelector, nil)...)
} else { // Case of id[expr] = expr
indexSelector := func(n ast.Node) bool {
_, ok := n.(*ast.IndexExpr)
return ok
}
f := func(n ast.Node) []ast.Node {
ie, ok := n.(*ast.IndexExpr)
if !ok { // not possible
return nil
}
return pick(ie.Index, varSelector, nil)
}
uses = append(uses, pickFromExpList(n.Lhs, indexSelector, f)...)
}
uses = append(uses, pickFromExpList(n.Rhs, varSelector, nil)...)
markParamListAsUsed(uses, v)
cs := v.sStk.currentScope()
cs.addVars(n.Lhs)
case *ast.Ident:
if n.Obj != nil {
if n.Obj.Kind.String() == "var" {
markParamAsUsed(n, v)
}
}
case *ast.ForStmt:
v.sStk.openScope()
if n.Init != nil {
ast.Walk(v, n.Init)
}
uses := pickFromExpList([]ast.Expr{n.Cond}, varSelector, nil)
markParamListAsUsed(uses, v)
ast.Walk(v, n.Body)
v.sStk.closeScope()
return nil
case *ast.SwitchStmt:
v.sStk.openScope()
if n.Init != nil {
ast.Walk(v, n.Init)
}
uses := pickFromExpList([]ast.Expr{n.Tag}, varSelector, nil)
markParamListAsUsed(uses, v)
// Analyze cases (they are not BlockStmt but a list of Stmt)
cases := n.Body.List
for _, c := range cases {
cc, ok := c.(*ast.CaseClause)
if !ok {
continue
}
uses := pickFromExpList(cc.List, varSelector, nil)
markParamListAsUsed(uses, v)
v.sStk.openScope()
for _, stmt := range cc.Body {
ast.Walk(v, stmt)
}
v.sStk.closeScope()
}
v.sStk.closeScope()
return nil
}
return v
}
func retrieveNamedParams(pl []*ast.Field) map[string]bool {
result := make(map[string]bool, len(pl))
for _, p := range pl {
for _, n := range p.Names {
if n.Name != "_" {
result[n.Name] = true
}
}
}
return result
}
func checkUnusedParams(w lintUnusedParamRule, params map[string]bool, n *ast.FuncDecl) {
for k, v := range params {
if v {
w.onFailure(lint.Failure{
Confidence: 0.8, // confidence is not 1.0 because of shadow variables
Node: n,
Category: "bad practice",
Failure: fmt.Sprintf("parameter '%s' seems to be unused, consider removing or renaming it as _", k),
})
}
}
}
func markParamListAsUsed(ids []ast.Node, v funcVisitor) {
for _, id := range ids {
markParamAsUsed(id.(*ast.Ident), v)
}
}
func markParamAsUsed(id *ast.Ident, v funcVisitor) { // TODO: constraint parameters to receive just a list of params and a scope stack
for _, s := range v.sStk.stk {
if s.vars[id.Name] {
return
}
}
if v.params[id.Name] {
v.params[id.Name] = false
}
}
func isOpAssign(aTok token.Token) bool {
return aTok == token.ADD_ASSIGN || aTok == token.AND_ASSIGN ||
aTok == token.MUL_ASSIGN || aTok == token.OR_ASSIGN ||
aTok == token.QUO_ASSIGN || aTok == token.REM_ASSIGN ||
aTok == token.SHL_ASSIGN || aTok == token.SHR_ASSIGN ||
aTok == token.SUB_ASSIGN || aTok == token.XOR_ASSIGN
}

View File

@ -1,8 +1,10 @@
package rule
import (
"bytes"
"fmt"
"go/ast"
"go/printer"
"go/token"
"go/types"
"regexp"
@ -61,47 +63,6 @@ func isCgoExported(f *ast.FuncDecl) bool {
return false
}
var commonInitialisms = map[string]bool{
"ACL": true,
"API": true,
"ASCII": true,
"CPU": true,
"CSS": true,
"DNS": true,
"EOF": true,
"GUID": true,
"HTML": true,
"HTTP": true,
"HTTPS": true,
"ID": true,
"IP": true,
"JSON": true,
"LHS": true,
"QPS": true,
"RAM": true,
"RHS": true,
"RPC": true,
"SLA": true,
"SMTP": true,
"SQL": true,
"SSH": true,
"TCP": true,
"TLS": true,
"TTL": true,
"UDP": true,
"UI": true,
"UID": true,
"UUID": true,
"URI": true,
"URL": true,
"UTF8": true,
"VM": true,
"XML": true,
"XMPP": true,
"XSRF": true,
"XSS": true,
}
var allCapsRE = regexp.MustCompile(`^[A-Z0-9_]+$`)
func isIdent(expr ast.Expr, ident string) bool {
@ -146,3 +107,85 @@ func srcLine(src []byte, p token.Position) string {
}
return string(src[lo:hi])
}
// pick yields a list of nodes by picking them from a sub-ast with root node n.
// Nodes are selected by applying the fselect function
// f function is applied to each selected node before inseting it in the final result.
// If f==nil then it defaults to the identity function (ie it returns the node itself)
func pick(n ast.Node, fselect func(n ast.Node) bool, f func(n ast.Node) []ast.Node) []ast.Node {
var result []ast.Node
if n == nil {
return result
}
if f == nil {
f = func(n ast.Node) []ast.Node { return []ast.Node{n} }
}
onSelect := func(n ast.Node) {
result = append(result, f(n)...)
}
p := picker{fselect: fselect, onSelect: onSelect}
ast.Walk(p, n)
return result
}
func pickFromExpList(l []ast.Expr, fselect func(n ast.Node) bool, f func(n ast.Node) []ast.Node) []ast.Node {
result := make([]ast.Node, 0)
for _, e := range l {
result = append(result, pick(e, fselect, f)...)
}
return result
}
type picker struct {
fselect func(n ast.Node) bool
onSelect func(n ast.Node)
}
func (p picker) Visit(node ast.Node) ast.Visitor {
if p.fselect == nil {
return nil
}
if p.fselect(node) {
p.onSelect(node)
}
return p
}
// isBoolOp returns true if the given token corresponds to
// a bool operator
func isBoolOp(t token.Token) bool {
switch t {
case token.LAND, token.LOR, token.EQL, token.NEQ:
return true
}
return false
}
const (
trueName = "true"
falseName = "false"
)
func isExprABooleanLit(n ast.Node) (lexeme string, ok bool) {
oper, ok := n.(*ast.Ident)
if !ok {
return "", false
}
return oper.Name, (oper.Name == trueName || oper.Name == falseName)
}
// gofmt returns a string representation of the expression.
func gofmt(x ast.Expr) string {
buf := bytes.Buffer{}
fs := token.NewFileSet()
printer.Fprint(&buf, fs, x)
return buf.String()
}

Some files were not shown because too many files have changed in this diff Show More