mirror of
https://github.com/mgechev/revive.git
synced 2024-12-02 09:02:23 +02:00
817 lines
48 KiB
Markdown
817 lines
48 KiB
Markdown
[![Build Status](https://github.com/mgechev/revive/actions/workflows/test.yaml/badge.svg)](https://github.com/mgechev/revive/actions/workflows/test.yaml)
|
|
|
|
# revive
|
|
|
|
Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. **`Revive` provides a framework for development of custom rules, and lets you define a strict preset for enhancing your development & code review processes**.
|
|
|
|
<p align="center">
|
|
<img src="./assets/logo.png" alt="" width="300">
|
|
<br>
|
|
Logo by <a href="https://github.com/hawkgs">Georgi Serev</a>
|
|
</p>
|
|
|
|
Here's how `revive` is different from `golint`:
|
|
|
|
- Allows to enable or disable rules using a configuration file.
|
|
- Allows to configure the linting rules with a TOML file.
|
|
- 2x faster running the same rules as golint.
|
|
- Provides functionality for disabling a specific rule or the entire linter for a file or a range of lines.
|
|
- `golint` allows this only for generated files.
|
|
- Optional type checking. Most rules in golint do not require type checking. If you disable them in the config file, revive will run over 6x faster than golint.
|
|
- Provides multiple formatters which let us customize the output.
|
|
- Allows to customize the return code for the entire linter or based on the failure of only some rules.
|
|
- _Everyone can extend it easily with custom rules or formatters._
|
|
- `Revive` provides more rules compared to `golint`.
|
|
|
|
## Who uses Revive
|
|
|
|
- [`tidb`](https://github.com/pingcap/tidb) - TiDB is a distributed HTAP database compatible with the MySQL protocol
|
|
- [`grafana`](https://github.com/grafana/grafana) - The tool for beautiful monitoring and metric analytics & dashboards for Graphite, InfluxDB & Prometheus & More
|
|
- [`etcd`](https://github.com/etcd-io/etcd) - Distributed reliable key-value store for the most critical data of a distributed system
|
|
- [`cadence`](https://github.com/uber/cadence) - Cadence is a distributed, scalable, durable, and highly available orchestration engine by Uber to execute asynchronous long-running business logic in a scalable and resilient way
|
|
- [`ferret`](https://github.com/MontFerret/ferret) - Declarative web scraping
|
|
- [`gopass`](https://github.com/gopasspw/gopass) - The slightly more awesome standard unix password manager for teams
|
|
- [`gitea`](https://github.com/go-gitea/gitea) - Git with a cup of tea, painless self-hosted git service
|
|
- [`excelize`](https://github.com/360EntSecGroup-Skylar/excelize) - Go library for reading and writing Microsoft Excel™ (XLSX) files
|
|
- [`aurora`](https://github.com/xuri/aurora) - aurora is a web-based Beanstalk queue server console written in Go
|
|
- [`soar`](https://github.com/XiaoMi/soar) - SQL Optimizer And Rewriter
|
|
- [`pyroscope`](https://github.com/pyroscope-io/pyroscope) - Continuous profiling platform
|
|
- [`gorush`](https://github.com/appleboy/gorush) - A push notification server written in Go (Golang).
|
|
- [`dry`](https://github.com/moncho/dry) - dry - A Docker manager for the terminal.
|
|
- [`go-echarts`](https://github.com/chenjiandongx/go-echarts) - The adorable charts library for Golang
|
|
- [`reviewdog`](https://github.com/reviewdog/reviewdog) - Automated code review tool integrated with any code analysis tools regardless of programming language
|
|
- [`rudder-server`](https://github.com/rudderlabs/rudder-server) - Privacy and Security focused Segment-alternative, in Golang and React.
|
|
- [`sklearn`](https://github.com/pa-m/sklearn) - A partial port of scikit-learn written in Go.
|
|
- [`protoc-gen-doc`](https://github.com/pseudomuto/protoc-gen-doc) - Documentation generator plugin for Google Protocol Buffers.
|
|
- [`llvm`](https://github.com/llir/llvm) - Library for interacting with LLVM IR in pure Go.
|
|
- [`jenkins-library`](https://github.com/SAP/jenkins-library) - Jenkins shared library for Continuous Delivery pipelines by SAP.
|
|
- [`pd`](https://github.com/tikv/pd) - Placement driver for TiKV.
|
|
- [`shellhub`](https://github.com/shellhub-io/shellhub) - ShellHub enables teams to easily access any Linux device behind firewall and NAT.
|
|
- [`lorawan-stack`](https://github.com/TheThingsNetwork/lorawan-stack) - The Things Network Stack for LoRaWAN V3
|
|
- [`gin-jwt`](https://github.com/appleboy/gin-jwt) - This is a JWT middleware for Gin framework.
|
|
- [`gofight`](https://github.com/appleboy/gofight) - Testing API Handler written in Golang.
|
|
- [`Beaver`](https://github.com/Clivern/Beaver) - A Real Time Messaging Server.
|
|
- [`ggz`](https://github.com/go-ggz/ggz) - An URL shortener service written in Golang
|
|
- [`Codeac.io`](https://www.codeac.io?ref=revive) - Automated code review service integrates with GitHub, Bitbucket and GitLab (even self-hosted) and helps you fight technical debt.
|
|
- [`DevLake`](https://github.com/apache/incubator-devlake) - Apache DevLake is an open-source dev data platform to ingest, analyze, and visualize the fragmented data from DevOps tools,which can distill insights to improve engineering productivity.
|
|
- [`checker`](https://github.com/cinar/checker) - Checker helps validating user input through rules defined in struct tags or directly through functions.
|
|
- [`milvus`](https://github.com/milvus-io/milvus) - A cloud-native vector database, storage for next generation AI applications.
|
|
|
|
*Open a PR to add your project*.
|
|
|
|
<p align="center">
|
|
<img src="./assets/demo.svg" alt="" width="700">
|
|
</p>
|
|
|
|
<!-- TOC -->
|
|
|
|
- [revive](#revive)
|
|
- [Installation](#installation)
|
|
- [Usage](#usage)
|
|
- [Docker](#docker)
|
|
- [Bazel](#bazel)
|
|
- [Text Editors](#text-editors)
|
|
- [GitHub Actions](#github-actions)
|
|
- [Continuous Integration](#continuous-integration)
|
|
- [Linter Aggregators](#linter-aggregators)
|
|
- [golangci-lint](#golangci-lint)
|
|
- [Command Line Flags](#command-line-flags)
|
|
- [Sample Invocations](#sample-invocations)
|
|
- [Comment Directives](#comment-directives)
|
|
- [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)
|
|
- [Plain](#plain)
|
|
- [Unix](#unix)
|
|
- [SARIF](#sarif)
|
|
- [Extensibility](#extensibility)
|
|
- [Custom Rule](#writing-a-custom-rule)
|
|
- [Example](#example)
|
|
- [Custom Formatter](#custom-formatter)
|
|
- [Speed Comparison](#speed-comparison)
|
|
- [golint](#golint)
|
|
- [revive](#revive)
|
|
- [Overriding colorization detection](#overriding-colorization-detection)
|
|
- [Contributors](#contributors)
|
|
- [License](#license)
|
|
|
|
<!-- /TOC -->
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
go install github.com/mgechev/revive@latest
|
|
```
|
|
|
|
or get a released executable from the [Releases](https://github.com/mgechev/revive/releases) page.
|
|
|
|
You can install the main branch (including the last commit) with:
|
|
```bash
|
|
go install github.com/mgechev/revive@master
|
|
```
|
|
|
|
## Usage
|
|
|
|
Since the default behavior of `revive` is compatible with `golint`, without providing any additional flags, the only difference you'd notice is faster execution.
|
|
|
|
`revive` supports a `-config` flag whose value should correspond to a TOML file describing which rules to use for `revive`'s linting. If not provided, `revive` will try to use a global config file (assumed to be located at `$HOME/revive.toml`). Otherwise, if no configuration TOML file is found then `revive` uses a built-in set of default linting rules.
|
|
|
|
### Docker
|
|
A volume needs to be mounted to share the current repository with the container.
|
|
Please refer to the [bind mounts Docker documentation](https://docs.docker.com/storage/bind-mounts/)
|
|
|
|
```bash
|
|
docker run -v "$(pwd)":/var/<repository> ghcr.io/mgechev/revive:v1.1.2-next -config /var/<repository>/revive.toml -formatter stylish ./var/kidle/...
|
|
```
|
|
|
|
- `-v` is for the volume
|
|
- `ghcr.io/mgechev/revive:v1.1.2-next ` is the image name and its version corresponding to `revive` command
|
|
- The provided flags are the same as the binary usage.
|
|
|
|
### Bazel
|
|
|
|
If you want to use revive with Bazel, take a look at the [rules](https://github.com/atlassian/bazel-tools/tree/master/gorevive) that Atlassian maintains.
|
|
|
|
### Text Editors
|
|
|
|
- Support for VSCode in [vscode-go](https://github.com/Microsoft/vscode-go/pull/1699).
|
|
- Support for GoLand via [File Watchers](https://dev.to/s0xzwasd/configure-revive-go-linter-in-goland-2ggl).
|
|
- Support for Atom via [linter-revive](https://github.com/morphy2k/linter-revive).
|
|
- Support for vim via [dense-analysis/ale](https://github.com/dense-analysis/ale).
|
|
|
|
```vim
|
|
let g:ale_linters = {
|
|
\ 'go': ['revive'],
|
|
\}
|
|
```
|
|
|
|
- Support for Neovim via [null-ls.nvim](https://github.com/jose-elias-alvarez/null-ls.nvim).
|
|
|
|
```lua
|
|
require("null-ls").setup({
|
|
sources = {
|
|
require("null-ls").builtins.diagnostics.revive
|
|
},
|
|
})
|
|
```
|
|
|
|
### GitHub Actions
|
|
|
|
- [Revive Action](https://github.com/marketplace/actions/revive-action) with annotation support
|
|
|
|
### Continuous Integration
|
|
|
|
[Codeac.io](https://www.codeac.io?ref=revive) - Automated code review service integrates with GitHub, Bitbucket and GitLab (even self-hosted) and helps you fight technical debt. Check your [pull-requests](https://www.codeac.io/documentation/pull-requests.html?ref=revive) with [revive](https://www.codeac.io/documentation/revive-configuration.html?ref=revive) automatically. (free for open-source projects)
|
|
|
|
### Linter aggregators
|
|
|
|
#### golangci-lint
|
|
To enable `revive` in `golangci-lint` you need to add `revive` to the list of enabled linters:
|
|
|
|
```yaml
|
|
# golangci-lint configuration file
|
|
linters:
|
|
enable:
|
|
- revive
|
|
```
|
|
Then `revive` can be configured by adding an entry to the `linters-settings` section of the configuration, for example:
|
|
|
|
```yaml
|
|
# golangci-lint configuration file
|
|
linters-settings:
|
|
revive:
|
|
ignore-generated-header: true
|
|
severity: warning
|
|
rules:
|
|
- name: atomic
|
|
- name: line-length-limit
|
|
severity: error
|
|
arguments: [80]
|
|
- name: unhandled-error
|
|
arguments : ["fmt.Printf", "myFunction"]
|
|
```
|
|
|
|
The above configuration enables three rules of `revive`: _atomic_, _line-length-limit_ and _unhandled-error_ and pass some arguments to the last two.
|
|
The [Configuration](#configuration) section of this document provides details on how to configure `revive`. Note that while `revive` configuration is in TOML, that of `golangci-lint` is in YAML.
|
|
|
|
Please notice that if no particular configuration is provided, `revive` will behave as `go-lint` does, i.e. all `go-lint` rules are enabled (the [Available Rules table](#available-rules) details what are the `go-lint` rules). When a configuration is provided, only rules in the configuration are enabled.
|
|
|
|
### Command Line Flags
|
|
|
|
`revive` accepts the following command line parameters:
|
|
|
|
- `-config [PATH]` - path to config file in TOML format, defaults to `$HOME/revive.toml` if present.
|
|
- `-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/).
|
|
- `-max_open_files` - maximum number of open files at the same time. Defaults to unlimited.
|
|
- `-set_exit_status` - set exit status to 1 if any issues are found, overwrites `errorCode` and `warningCode` in config.
|
|
- `-version` - get revive version.
|
|
|
|
|
|
### Sample Invocations
|
|
|
|
```shell
|
|
revive -config revive.toml -exclude file1.go -exclude file2.go -formatter friendly github.com/mgechev/revive package/...
|
|
```
|
|
|
|
- The command above will use the configuration from `revive.toml`
|
|
- `revive` will ignore `file1.go` and `file2.go`
|
|
- The output will be formatted with the `friendly` formatter
|
|
- The linter will analyze `github.com/mgechev/revive` and the files in `package`
|
|
|
|
### Comment Directives
|
|
|
|
Using comments, you can disable the linter for the entire file or only range of lines:
|
|
|
|
```go
|
|
//revive:disable
|
|
|
|
func Public() {}
|
|
//revive:enable
|
|
```
|
|
|
|
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
|
|
//revive:disable:unexported-return
|
|
func Public() private {
|
|
return private
|
|
}
|
|
//revive:enable:unexported-return
|
|
```
|
|
|
|
This way, `revive` will not warn you for that you're returning an object of an unexported type, from an exported function.
|
|
|
|
You can document why you disable the linter by adding a trailing text in the directive, for example
|
|
|
|
```go
|
|
//revive:disable Until the code is stable
|
|
```
|
|
```go
|
|
//revive:disable:cyclomatic High complexity score but easy to understand
|
|
```
|
|
|
|
You can also configure `revive` to enforce documenting linter disabling directives by adding
|
|
|
|
```toml
|
|
[directive.specify-disable-reason]
|
|
```
|
|
|
|
in the configuration. You can set the severity (defaults to _warning_) of the violation of this directive
|
|
|
|
```toml
|
|
[directive.specify-disable-reason]
|
|
severity = "error"
|
|
```
|
|
|
|
### Configuration
|
|
|
|
`revive` can be configured with a TOML file. Here's a sample configuration with explanation for the individual properties:
|
|
|
|
```toml
|
|
# When set to false, ignores files with "GENERATED" header, similar to golint
|
|
ignoreGeneratedHeader = true
|
|
|
|
# Sets the default severity to "warning"
|
|
severity = "warning"
|
|
|
|
# Sets the default failure confidence. This means that linting errors
|
|
# with less than 0.8 confidence will be ignored.
|
|
confidence = 0.8
|
|
|
|
# Sets the error code for failures with severity "error"
|
|
errorCode = 0
|
|
|
|
# Sets the error code for failures with severity "warning"
|
|
warningCode = 0
|
|
|
|
# Configuration of the `cyclomatic` rule. Here we specify that
|
|
# the rule should fail if it detects code with higher complexity than 10.
|
|
[rule.cyclomatic]
|
|
arguments = [10]
|
|
|
|
# Sets the severity of the `package-comments` rule to "error".
|
|
[rule.package-comments]
|
|
severity = "error"
|
|
```
|
|
|
|
By default `revive` will enable only the linting rules that are named in the configuration file.
|
|
For example, the previous configuration file makes `revive` to enable only _cyclomatic_ and _package-comments_ linting rules.
|
|
|
|
To enable all available rules you need to add:
|
|
|
|
```toml
|
|
enableAllRules = true
|
|
```
|
|
|
|
This will enable all available rules no matter of what rules are named in the configuration file.
|
|
|
|
To disable a rule, you simply mark it as disabled in the configuration.
|
|
For example:
|
|
|
|
```toml
|
|
[rule.line-length-limit]
|
|
Disabled = true
|
|
```
|
|
When enabling all rules you still need/can provide specific configurations for rules.
|
|
The following files is an example configuration were all rules are enabled, with exception to those that are explicitly disabled, and some rules are configured with particular arguments:
|
|
|
|
```toml
|
|
severity = "warning"
|
|
confidence = 0.8
|
|
errorCode = 0
|
|
warningCode = 0
|
|
|
|
# Enable all available rules
|
|
enableAllRules = true
|
|
|
|
# Disabled rules
|
|
[rule.blank-imports]
|
|
Disabled = true
|
|
[rule.file-header]
|
|
Disabled = true
|
|
[rule.max-public-structs]
|
|
Disabled = true
|
|
[rule.line-length-limit]
|
|
Disabled = true
|
|
[rule.function-length]
|
|
Disabled = true
|
|
[rule.banned-characters]
|
|
Disabled = true
|
|
|
|
# Rule tuning
|
|
[rule.argument-limit]
|
|
Arguments = [5]
|
|
[rule.cyclomatic]
|
|
Arguments = [10]
|
|
[rule.cognitive-complexity]
|
|
Arguments = [7]
|
|
[rule.function-result-limit]
|
|
Arguments = [3]
|
|
[rule.error-strings]
|
|
Arguments = ["mypackage.Error"]
|
|
```
|
|
|
|
### Default Configuration
|
|
|
|
The default configuration of `revive` can be found at `defaults.toml`. This will enable all rules available in `golint` and use their default configuration (i.e. the way they are hardcoded in `golint`).
|
|
|
|
```shell
|
|
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.
|
|
|
|
### Custom Configuration
|
|
|
|
```shell
|
|
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.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]
|
|
```
|
|
|
|
### Rule-level file excludes
|
|
|
|
You also can setup custom excludes for each rule.
|
|
|
|
It's alternative for global `-exclude` program arg.
|
|
|
|
```toml
|
|
ignoreGeneratedHeader = false
|
|
severity = "warning"
|
|
confidence = 0.8
|
|
errorCode = 0
|
|
warningCode = 0
|
|
|
|
[rule.blank-imports]
|
|
Exclude=["**/*.pb.go"]
|
|
[rule.context-as-argument]
|
|
Exclude=["src/somepkg/*.go", "TEST"]
|
|
```
|
|
|
|
You can use following exclude patterns
|
|
|
|
1. full paths to files `src/pkg/mypkg/some.go`
|
|
2. globs `src/**/*.pb.go`
|
|
3. regexes (should have prefix ~) `~\.(pb|auto|generated)\.go$`
|
|
4. well-known `TEST` (same as `**/*_test.go`)
|
|
5. special cases:
|
|
a. `*` and `~` patterns exclude all files (same effect than disabling the rule)
|
|
b. `""` (empty) pattern excludes nothing
|
|
|
|
> NOTE: do not mess with `exclude` that can be used at top level of TOML file, that mean "exclude package patterns", not "exclude file patterns"
|
|
|
|
## 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`](./RULES_DESCRIPTIONS.md#context-key-types) | n/a | Disallows the usage of basic types in `context.WithValue`. | yes | yes |
|
|
| [`time-equal`](./RULES_DESCRIPTIONS.md#time-equal) | n/a | Suggests to use `time.Time.Equal` instead of `==` and `!=` for equality check time. | no | yes |
|
|
| [`time-naming`](./RULES_DESCRIPTIONS.md#time-naming) | n/a | Conventions around the naming of time variables. | yes | yes |
|
|
| [`unchecked-type-assertions`](./RULES_DESCRIPTIONS.md#unchecked-type-assertions) | n/a | Disallows type assertions without checking the result. | no | 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) | []string | 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) | []string | Naming and commenting conventions on exported symbols. | yes | no |
|
|
| [`if-return`](./RULES_DESCRIPTIONS.md#if-return) | n/a | Redundant if when returning an error. | no | 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) | []string | Prevents redundant else statements. | yes | no |
|
|
| [`argument-limit`](./RULES_DESCRIPTIONS.md#argument-limit) | int (defaults to 8) | Specifies the maximum number of arguments a function can receive | no | no |
|
|
| [`cyclomatic`](./RULES_DESCRIPTIONS.md#cyclomatic) | int (defaults to 10) | Sets restriction for maximum Cyclomatic complexity. | no | no |
|
|
| [`max-public-structs`](./RULES_DESCRIPTIONS.md#max-public-structs) | int (defaults to 5) | The maximum number of public structs in a file. | no | no |
|
|
| [`file-header`](./RULES_DESCRIPTIONS.md#file-header) | string (defaults to none)| Header which each file should have. | no | no |
|
|
| [`empty-block`](./RULES_DESCRIPTIONS.md#empty-block) | n/a | Warns on empty code blocks | no | yes |
|
|
| [`superfluous-else`](./RULES_DESCRIPTIONS.md#superfluous-else) | []string | 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) | []string | 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 (defaults to 3)| 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 |
|
|
| [`range-val-address`](./RULES_DESCRIPTIONS.md#range-val-address)| n/a | Warns if address of range value is used dangerously | no | yes |
|
|
| [`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-length-limit`](./RULES_DESCRIPTIONS.md#line-length-limit) | int (defaults to 80) | Specifies the maximum number of characters in a line | no | no |
|
|
| [`call-to-gc`](./RULES_DESCRIPTIONS.md#call-to-gc) | n/a | Warns on explicit call to the garbage collector | no | no |
|
|
| [`duplicated-imports`](./RULES_DESCRIPTIONS.md#duplicated-imports) | n/a | Looks for packages that are imported two or more times | no | no |
|
|
| [`import-shadowing`](./RULES_DESCRIPTIONS.md#import-shadowing) | n/a | Spots identifiers that shadow an import | no | no |
|
|
| [`bare-return`](./RULES_DESCRIPTIONS.md#bare-return) | n/a | Warns on bare returns | no | no |
|
|
| [`unused-receiver`](./RULES_DESCRIPTIONS.md#unused-receiver) | n/a | Suggests to rename or remove unused method receivers | no | no |
|
|
| [`unhandled-error`](./RULES_DESCRIPTIONS.md#unhandled-error) | []string | Warns on unhandled errors returned by function calls | no | yes |
|
|
| [`cognitive-complexity`](./RULES_DESCRIPTIONS.md#cognitive-complexity) | int (defaults to 7) | Sets restriction for maximum Cognitive complexity. | no | no |
|
|
| [`string-of-int`](./RULES_DESCRIPTIONS.md#string-of-int) | n/a | Warns on suspicious casts from int to string | no | yes |
|
|
| [`string-format`](./RULES_DESCRIPTIONS.md#string-format) | map | Warns on specific string literals that fail one or more user-configured regular expressions | no | no |
|
|
| [`early-return`](./RULES_DESCRIPTIONS.md#early-return) | []string | Spots if-then-else statements where the predicate may be inverted to reduce nesting | no | no |
|
|
| [`unconditional-recursion`](./RULES_DESCRIPTIONS.md#unconditional-recursion) | n/a | Warns on function calls that will lead to (direct) infinite recursion | no | no |
|
|
| [`identical-branches`](./RULES_DESCRIPTIONS.md#identical-branches) | n/a | Spots if-then-else statements with identical `then` and `else` branches | no | no |
|
|
| [`defer`](./RULES_DESCRIPTIONS.md#defer) | map | Warns on some [defer gotchas](https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1) | no | no |
|
|
| [`unexported-naming`](./RULES_DESCRIPTIONS.md#unexported-naming) | n/a | Warns on wrongly named un-exported symbols | no | no |
|
|
| [`function-length`](./RULES_DESCRIPTIONS.md#function-length) | int, int (defaults to 50 statements, 75 lines) | Warns on functions exceeding the statements or lines max | no | no |
|
|
| [`nested-structs`](./RULES_DESCRIPTIONS.md#nested-structs) | n/a | Warns on structs within structs | no | no |
|
|
| [`useless-break`](./RULES_DESCRIPTIONS.md#useless-break) | n/a | Warns on useless `break` statements in case clauses | no | no |
|
|
| [`banned-characters`](./RULES_DESCRIPTIONS.md#banned-characters) | []string (defaults to []string{}) | Checks banned characters in identifiers | no | no |
|
|
| [`optimize-operands-order`](./RULES_DESCRIPTIONS.md#optimize-operands-order) | n/a | Checks inefficient conditional expressions | no | no |
|
|
| [`use-any`](./RULES_DESCRIPTIONS.md#use-any) | n/a | Proposes to replace `interface{}` with its alias `any` | no | no |
|
|
| [`datarace`](./RULES_DESCRIPTIONS.md#datarace) | n/a | Spots potential dataraces | no | no |
|
|
| [`comment-spacings`](./RULES_DESCRIPTIONS.md#comment-spacings) | []string | Warns on malformed comments | no | no |
|
|
| [`redundant-import-alias`](./RULES_DESCRIPTIONS.md#redundant-import-alias) | n/a | Warns on import aliases matching the imported package name | no | no |
|
|
| [`import-alias-naming`](./RULES_DESCRIPTIONS.md#import-alias-naming) | string or map[string]string (defaults to allow regex pattern ^[a-z][a-z0-9]{0,}$) | Conventions around the naming of import aliases. | no | no |
|
|
| [`enforce-map-style`](./RULES_DESCRIPTIONS.md#enforce-map-style) | string (defaults to "any") | Enforces consistent usage of `make(map[type]type)` or `map[type]type{}` for map initialization. Does not affect `make(map[type]type, size)` constructions. | no | no |
|
|
| [`enforce-slice-style`](./RULES_DESCRIPTIONS.md#enforce-slice-style) | string (defaults to "any") | Enforces consistent usage of `make([]type, 0)` or `[]type{}` for slice initialization. Does not affect `make(map[type]type, non_zero_len, or_non_zero_cap)` constructions. | no | no |
|
|
|
|
|
|
## Configurable rules
|
|
|
|
Here you can find how you can configure some 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
|
|
|
|
This section lists all the available formatters and provides a screenshot for each one.
|
|
|
|
### Friendly
|
|
|
|
![Friendly formatter](/assets/formatter-friendly.png)
|
|
|
|
### Stylish
|
|
|
|
![Stylish formatter](/assets/formatter-stylish.png)
|
|
|
|
### Default
|
|
|
|
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)
|
|
|
|
### SARIF
|
|
The `sarif` formatter produces outputs in SARIF, for _Static Analysis Results Interchange Format_, a standard JSON-based format for the output of static analysis tools defined and promoted by [OASIS](https://www.oasis-open.org/).
|
|
|
|
Current supported version of the standard is [SARIF-v2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/csprd01/sarif-v2.1.0-csprd01.html
|
|
).
|
|
|
|
## Extensibility
|
|
|
|
The tool can be extended with custom rules or formatters. This section contains additional information on how to implement such.
|
|
|
|
To extend the linter with a custom rule you can push it to this repository or use `revive` as a library (see below)
|
|
|
|
To add a custom formatter you'll have to push it to this repository or fork it. This is due to the limited `-buildmode=plugin` support which [works only on Linux (with known issues)](https://golang.org/pkg/plugin/).
|
|
|
|
### Writing a Custom Rule
|
|
|
|
Each rule needs to implement the `lint.Rule` interface:
|
|
|
|
```go
|
|
type Rule interface {
|
|
Name() string
|
|
Apply(*File, Arguments) []Failure
|
|
}
|
|
```
|
|
|
|
The `Arguments` type is an alias of the type `[]interface{}`. The arguments of the rule are passed from the configuration file.
|
|
|
|
#### Example
|
|
|
|
Let's suppose we have developed a rule called `BanStructNameRule` which disallow us to name a structure with given identifier. We can set the banned identifier by using the TOML configuration file:
|
|
|
|
```toml
|
|
[rule.ban-struct-name]
|
|
arguments = ["Foo"]
|
|
```
|
|
|
|
With the snippet above we:
|
|
|
|
- Enable the rule with name `ban-struct-name`. The `Name()` method of our rule should return a string which matches `ban-struct-name`.
|
|
- Configure the rule with the argument `Foo`. The list of arguments will be passed to `Apply(*File, Arguments)` together with the target file we're linting currently.
|
|
|
|
A sample rule implementation can be found [here](/rule/argument-limit.go).
|
|
|
|
#### Using `revive` as a library
|
|
If a rule is specific to your use case
|
|
(i.e. it is not a good candidate to be added to `revive`'s rule set) you can add it to your own linter using `revive` as linting engine.
|
|
|
|
The following code shows how to use `revive` in your own application.
|
|
In the example only one rule is added (`myRule`), of course, you can add as many as you need to.
|
|
Your rules can be configured programmatically or with the standard `revive` configuration file.
|
|
The full rule set of `revive` is also actionable by your application.
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"github.com/mgechev/revive/cli"
|
|
"github.com/mgechev/revive/lint"
|
|
"github.com/mgechev/revive/revivelib"
|
|
)
|
|
|
|
func main() {
|
|
cli.RunRevive(revivelib.NewExtraRule(&myRule{}, lint.RuleConfig{}))
|
|
}
|
|
|
|
type myRule struct{}
|
|
|
|
func (f myRule) Name() string {
|
|
return "myRule"
|
|
}
|
|
|
|
func (f myRule) Apply(*lint.File, lint.Arguments) []lint.Failure { ... }
|
|
```
|
|
|
|
You can still go further and use `revive` without its cli, as part of your library, or your own cli:
|
|
|
|
```go
|
|
package mylib
|
|
|
|
import (
|
|
"github.com/mgechev/revive/cli"
|
|
"github.com/mgechev/revive/revivelib"
|
|
"github.com/mgechev/revive/lint"
|
|
)
|
|
|
|
// Error checking removed for clarity
|
|
func LintMyFile(file string) {
|
|
conf, _:= config.GetConfig("../defaults.toml")
|
|
|
|
revive, _ := revivelib.New(
|
|
conf, // Configuration file
|
|
true, // Set exit status
|
|
2048, // Max open files
|
|
|
|
// Then add as many extra rules as you need
|
|
revivelib.NewExtraRule(&myRule{}, lint.RuleConfig{}),
|
|
)
|
|
|
|
failuresChan, err := revive.Lint(
|
|
revivelib.Include(file),
|
|
revivelib.Exclude("./fixtures"),
|
|
// You can use as many revivelib.Include or revivelib.Exclude as required
|
|
)
|
|
if err != nil {
|
|
panic("Shouldn't have failed: " + err.Error)
|
|
}
|
|
|
|
// Now let's return the formatted errors
|
|
failures, exitCode, _ := revive.Format("stylish", failuresChan)
|
|
|
|
// failures is the string with all formatted lint error messages
|
|
// exit code is 0 if no errors, 1 if errors (unless config options change it)
|
|
// ... do something with them
|
|
}
|
|
|
|
type myRule struct{}
|
|
|
|
func (f myRule) Name() string {
|
|
return "myRule"
|
|
}
|
|
|
|
func (f myRule) Apply(*lint.File, lint.Arguments) []lint.Failure { ... }
|
|
```
|
|
|
|
### Custom Formatter
|
|
|
|
Each formatter needs to implement the following interface:
|
|
|
|
```go
|
|
type Formatter interface {
|
|
Format(<-chan Failure, Config) (string, error)
|
|
Name() string
|
|
}
|
|
```
|
|
|
|
The `Format` method accepts a channel of `Failure` instances and the configuration of the enabled rules. The `Name()` method should return a string different from the names of the already existing rules. This string is used when specifying the formatter when invoking the `revive` CLI tool.
|
|
|
|
For a sample formatter, take a look at [this file](/formatter/json.go).
|
|
|
|
## Speed Comparison
|
|
|
|
Compared to `golint`, `revive` performs better because it lints the files for each individual rule into a separate goroutine. Here's a basic performance benchmark on MacBook Pro Early 2013 run on kubernetes:
|
|
|
|
### golint
|
|
|
|
```shell
|
|
time golint kubernetes/... > /dev/null
|
|
|
|
real 0m54.837s
|
|
user 0m57.844s
|
|
sys 0m9.146s
|
|
```
|
|
|
|
### revive
|
|
|
|
```shell
|
|
# no type checking
|
|
time revive -config untyped.toml kubernetes/... > /dev/null
|
|
|
|
real 0m8.471s
|
|
user 0m40.721s
|
|
sys 0m3.262s
|
|
```
|
|
|
|
Keep in mind that if you use rules which require type checking, the performance may drop to 2x faster than `golint`:
|
|
|
|
```shell
|
|
# type checking enabled
|
|
time revive kubernetes/... > /dev/null
|
|
|
|
real 0m26.211s
|
|
user 2m6.708s
|
|
sys 0m17.192s
|
|
```
|
|
|
|
Currently, type checking is enabled by default. If you want to run the linter without type checking, remove all typed rules from the configuration file.
|
|
|
|
## Overriding colorization detection
|
|
|
|
By default, `revive` determines whether or not to colorize its output based on whether it's connected to a TTY or not.
|
|
This works for most use cases, but may not behave as expected if you use `revive` in a pipeline of commands,
|
|
where STDOUT is being piped to another command.
|
|
|
|
To force colorization, add `REVIVE_FORCE_COLOR=1` to the environment you're running in. For example:
|
|
|
|
```shell
|
|
REVIVE_FORCE_COLOR=1 revive -formatter friendly ./... | tee revive.log
|
|
```
|
|
|
|
## Contributors
|
|
|
|
[<img alt="renovate[bot]" src="https://avatars.githubusercontent.com/in/2740?v=4&s=117" width="117">](https://github.com/apps/renovate) |[<img alt="mgechev" src="https://avatars.githubusercontent.com/u/455023?v=4&s=117" width="117">](https://github.com/mgechev) |[<img alt="chavacava" src="https://avatars.githubusercontent.com/u/25788468?v=4&s=117" width="117">](https://github.com/chavacava) |[<img alt="renovate-bot" src="https://avatars.githubusercontent.com/u/25180681?v=4&s=117" width="117">](https://github.com/renovate-bot) |[<img alt="xuri" src="https://avatars.githubusercontent.com/u/2809468?v=4&s=117" width="117">](https://github.com/xuri) |[<img alt="doniacld" src="https://avatars.githubusercontent.com/u/19799268?v=4&s=117" width="117">](https://github.com/doniacld) |
|
|
:---: |:---: |:---: |:---: |:---: |:---: |
|
|
[renovate[bot]](https://github.com/apps/renovate) |[mgechev](https://github.com/mgechev) |[chavacava](https://github.com/chavacava) |[renovate-bot](https://github.com/renovate-bot) |[xuri](https://github.com/xuri) |[doniacld](https://github.com/doniacld) |
|
|
|
|
[<img alt="Clivern" src="https://avatars.githubusercontent.com/u/1634427?v=4&s=117" width="117">](https://github.com/Clivern) |[<img alt="bernhardreisenberger" src="https://avatars.githubusercontent.com/u/5809300?v=4&s=117" width="117">](https://github.com/bernhardreisenberger) |[<img alt="morphy2k" src="https://avatars.githubusercontent.com/u/4280578?v=4&s=117" width="117">](https://github.com/morphy2k) |[<img alt="butuzov" src="https://avatars.githubusercontent.com/u/651824?v=4&s=117" width="117">](https://github.com/butuzov) |[<img alt="dshemin" src="https://avatars.githubusercontent.com/u/11780307?v=4&s=117" width="117">](https://github.com/dshemin) |[<img alt="heynemann" src="https://avatars.githubusercontent.com/u/60965?v=4&s=117" width="117">](https://github.com/heynemann) |
|
|
:---: |:---: |:---: |:---: |:---: |:---: |
|
|
[Clivern](https://github.com/Clivern) |[bernhardreisenberger](https://github.com/bernhardreisenberger) |[morphy2k](https://github.com/morphy2k) |[butuzov](https://github.com/butuzov) |[dshemin](https://github.com/dshemin) |[heynemann](https://github.com/heynemann) |
|
|
|
|
[<img alt="gsamokovarov" src="https://avatars.githubusercontent.com/u/604618?v=4&s=117" width="117">](https://github.com/gsamokovarov) |[<img alt="tymonx" src="https://avatars.githubusercontent.com/u/8367378?v=4&s=117" width="117">](https://github.com/tymonx) |[<img alt="sina-devel" src="https://avatars.githubusercontent.com/u/61763643?v=4&s=117" width="117">](https://github.com/sina-devel) |[<img alt="abeltay" src="https://avatars.githubusercontent.com/u/15604207?v=4&s=117" width="117">](https://github.com/abeltay) |[<img alt="dvejmz" src="https://avatars.githubusercontent.com/u/9487006?v=4&s=117" width="117">](https://github.com/dvejmz) |[<img alt="mihaitodor" src="https://avatars.githubusercontent.com/u/788216?v=4&s=117" width="117">](https://github.com/mihaitodor) |
|
|
:---: |:---: |:---: |:---: |:---: |:---: |
|
|
[gsamokovarov](https://github.com/gsamokovarov) |[tymonx](https://github.com/tymonx) |[sina-devel](https://github.com/sina-devel) |[abeltay](https://github.com/abeltay) |[dvejmz](https://github.com/dvejmz) |[mihaitodor](https://github.com/mihaitodor) |
|
|
|
|
[<img alt="markelog" src="https://avatars.githubusercontent.com/u/945528?v=4&s=117" width="117">](https://github.com/markelog) |[<img alt="tamird" src="https://avatars.githubusercontent.com/u/1535036?v=4&s=117" width="117">](https://github.com/tamird) |[<img alt="shmsr" src="https://avatars.githubusercontent.com/u/51480165?v=4&s=117" width="117">](https://github.com/shmsr) |[<img alt="mapreal19" src="https://avatars.githubusercontent.com/u/3055997?v=4&s=117" width="117">](https://github.com/mapreal19) |[<img alt="kulti" src="https://avatars.githubusercontent.com/u/1286683?v=4&s=117" width="117">](https://github.com/kulti) |[<img alt="Aragur" src="https://avatars.githubusercontent.com/u/11004008?v=4&s=117" width="117">](https://github.com/Aragur) |
|
|
:---: |:---: |:---: |:---: |:---: |:---: |
|
|
[markelog](https://github.com/markelog) |[tamird](https://github.com/tamird) |[shmsr](https://github.com/shmsr) |[mapreal19](https://github.com/mapreal19) |[kulti](https://github.com/kulti) |[Aragur](https://github.com/Aragur) |
|
|
|
|
[<img alt="s0xzwasd" src="https://avatars.githubusercontent.com/u/29286703?v=4&s=117" width="117">](https://github.com/s0xzwasd) |[<img alt="derekperkins" src="https://avatars.githubusercontent.com/u/3588778?v=4&s=117" width="117">](https://github.com/derekperkins) |[<img alt="yangdiangzb" src="https://avatars.githubusercontent.com/u/16643665?v=4&s=117" width="117">](https://github.com/yangdiangzb) |[<img alt="petethepig" src="https://avatars.githubusercontent.com/u/662636?v=4&s=117" width="117">](https://github.com/petethepig) |[<img alt="euank" src="https://avatars.githubusercontent.com/u/2147649?v=4&s=117" width="117">](https://github.com/euank) |[<img alt="quasilyte" src="https://avatars.githubusercontent.com/u/6286655?v=4&s=117" width="117">](https://github.com/quasilyte) |
|
|
:---: |:---: |:---: |:---: |:---: |:---: |
|
|
[s0xzwasd](https://github.com/s0xzwasd) |[derekperkins](https://github.com/derekperkins) |[yangdiangzb](https://github.com/yangdiangzb) |[petethepig](https://github.com/petethepig) |[euank](https://github.com/euank) |[quasilyte](https://github.com/quasilyte) |
|
|
|
|
[<img alt="grongor" src="https://avatars.githubusercontent.com/u/972493?v=4&s=117" width="117">](https://github.com/grongor) |[<img alt="jamesmaidment" src="https://avatars.githubusercontent.com/u/2050324?v=4&s=117" width="117">](https://github.com/jamesmaidment) |[<img alt="jan-xyz" src="https://avatars.githubusercontent.com/u/5249233?v=4&s=117" width="117">](https://github.com/jan-xyz) |[<img alt="jefersonf" src="https://avatars.githubusercontent.com/u/3049540?v=4&s=117" width="117">](https://github.com/jefersonf) |[<img alt="johnrichardrinehart" src="https://avatars.githubusercontent.com/u/6321578?v=4&s=117" width="117">](https://github.com/johnrichardrinehart) |[<img alt="very-amused" src="https://avatars.githubusercontent.com/u/44382255?v=4&s=117" width="117">](https://github.com/very-amused) |
|
|
:---: |:---: |:---: |:---: |:---: |:---: |
|
|
[grongor](https://github.com/grongor) |[jamesmaidment](https://github.com/jamesmaidment) |[jan-xyz](https://github.com/jan-xyz) |[jefersonf](https://github.com/jefersonf) |[johnrichardrinehart](https://github.com/johnrichardrinehart) |[very-amused](https://github.com/very-amused) |
|
|
|
|
[<img alt="ldez" src="https://avatars.githubusercontent.com/u/5674651?v=4&s=117" width="117">](https://github.com/ldez) |[<img alt="mathieu-aubin" src="https://avatars.githubusercontent.com/u/15820228?v=4&s=117" width="117">](https://github.com/mathieu-aubin) |[<img alt="michalhisim" src="https://avatars.githubusercontent.com/u/764249?v=4&s=117" width="117">](https://github.com/michalhisim) |[<img alt="pa-m" src="https://avatars.githubusercontent.com/u/5503106?v=4&s=117" width="117">](https://github.com/pa-m) |[<img alt="paul-at-start" src="https://avatars.githubusercontent.com/u/5486775?v=4&s=117" width="117">](https://github.com/paul-at-start) |[<img alt="weastur" src="https://avatars.githubusercontent.com/u/10865586?v=4&s=117" width="117">](https://github.com/weastur) |
|
|
:---: |:---: |:---: |:---: |:---: |:---: |
|
|
[ldez](https://github.com/ldez) |[mathieu-aubin](https://github.com/mathieu-aubin) |[michalhisim](https://github.com/michalhisim) |[pa-m](https://github.com/pa-m) |[paul-at-start](https://github.com/paul-at-start) |[weastur](https://github.com/weastur) |
|
|
|
|
[<img alt="paco0x" src="https://avatars.githubusercontent.com/u/6123425?v=4&s=117" width="117">](https://github.com/paco0x) |[<img alt="rdeusser" src="https://avatars.githubusercontent.com/u/5935071?v=4&s=117" width="117">](https://github.com/rdeusser) |[<img alt="ridvansumset" src="https://avatars.githubusercontent.com/u/26631560?v=4&s=117" width="117">](https://github.com/ridvansumset) |[<img alt="tartale" src="https://avatars.githubusercontent.com/u/9323250?v=4&s=117" width="117">](https://github.com/tartale) |[<img alt="Jarema" src="https://avatars.githubusercontent.com/u/7369771?v=4&s=117" width="117">](https://github.com/Jarema) |[<img alt="vkrol" src="https://avatars.githubusercontent.com/u/153412?v=4&s=117" width="117">](https://github.com/vkrol) |
|
|
:---: |:---: |:---: |:---: |:---: |:---: |
|
|
[paco0x](https://github.com/paco0x) |[rdeusser](https://github.com/rdeusser) |[ridvansumset](https://github.com/ridvansumset) |[tartale](https://github.com/tartale) |[Jarema](https://github.com/Jarema) |[vkrol](https://github.com/vkrol) |
|
|
|
|
[<img alt="cce" src="https://avatars.githubusercontent.com/u/51567?v=4&s=117" width="117">](https://github.com/cce) |[<img alt="haya14busa" src="https://avatars.githubusercontent.com/u/3797062?v=4&s=117" width="117">](https://github.com/haya14busa) |[<img alt="jmckenzieark" src="https://avatars.githubusercontent.com/u/70923399?v=4&s=117" width="117">](https://github.com/jmckenzieark) |[<img alt="techknowlogick" src="https://avatars.githubusercontent.com/u/164197?v=4&s=117" width="117">](https://github.com/techknowlogick) |[<img alt="zeripath" src="https://avatars.githubusercontent.com/u/1824502?v=4&s=117" width="117">](https://github.com/zeripath) |
|
|
:---: |:---: |:---: |:---: |:---: |
|
|
[cce](https://github.com/cce) |[haya14busa](https://github.com/haya14busa) |[jmckenzieark](https://github.com/jmckenzieark) |[techknowlogick](https://github.com/techknowlogick) |[zeripath](https://github.com/zeripath) |
|
|
|
|
## License
|
|
|
|
MIT
|