1
0
mirror of https://github.com/mgechev/revive.git synced 2025-11-23 22:04:49 +02:00

rule: allow lowercased and kebab-cased options (#1272)

* rule: tests for Configure with named options; fix errors

* rule: refactor and add tests for ifelse rules

* rule: allow lowercased and kebab-cased options

* test: update integration tests with lowercased params

* docs: update rules descriptions

* rule: simplify Configure implementation with one option

* gofmt and fix lint

* review: add isRuleOption, update grammar in doc, simplify regex

Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com>

---------

Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com>
This commit is contained in:
Oleksandr Redko
2025-03-28 01:34:20 -07:00
committed by GitHub
parent 43a44af30f
commit 9f5f957b33
54 changed files with 2530 additions and 215 deletions

View File

@@ -95,19 +95,24 @@ _Description_: Suggests using constant for [magic numbers](https://en.wikipedia.
_Configuration_:
* `maxLitCount` : (string) maximum number of instances of a string literal that are tolerated before warn.
* `allowStrs`: (string) comma-separated list of allowed string literals
* `allowInts`: (string) comma-separated list of allowed integers
* `allowFloats`: (string) comma-separated list of allowed floats
* `ignoreFuncs`: (string) comma-separated list of function names regexp patterns to exclude
* `maxLitCount` (`maxlitcount`, `max-lit-count`): (string) maximum number of instances of a string literal that are tolerated before warn.
* `allowStrs` (`allowstrs`, `allow-strs`): (string) comma-separated list of allowed string literals
* `allowInts` (`allowints`, `allow-ints`): (string) comma-separated list of allowed integers
* `allowFloats` (`allowfloats`, `allow-floats`): (string) comma-separated list of allowed floats
* `ignoreFuncs` (`ignorefuncs`, `ignore-funcs`): (string) comma-separated list of function names regexp patterns to exclude
Example:
Examples:
```toml
[rule.add-constant]
arguments = [{ maxLitCount = "3", allowStrs = "\"\"", allowInts = "0,1,2", allowFloats = "0.0,0.,1.0,1.,2.0,2.", ignoreFuncs = "os\\.*,fmt\\.Println,make" }]
```
```toml
[rule.add-constant]
arguments = [{ max-lit-count = "3", allow-strs = "\"\"", allow-ints = "0,1,2", allow-floats = "0.0,0.,1.0,1.,2.0,2.", ignore-funcs = "os\\.*,fmt\\.Println,make" }]
```
## argument-limit
_Description_: Warns when a function receives more parameters than the maximum set by the rule's configuration.
@@ -240,15 +245,20 @@ _Description_: By [convention](https://go.dev/wiki/CodeReviewComments#contexts),
_Configuration_:
* `allowTypesBefore` : (string) comma-separated list of types that may be before 'context.Context'
* `allowTypesBefore` (`allowtypesbefore`, `allow-types-before`): (string) comma-separated list of types that may be before 'context.Context'
Example:
Examples:
```toml
[rule.context-as-argument]
arguments = [{allowTypesBefore = "*testing.T,*github.com/user/repo/testing.Harness"}]
```
```toml
[rule.context-as-argument]
arguments = [{allow-types-before = "*testing.T,*github.com/user/repo/testing.Harness"}]
```
## context-keys-type
_Description_: Basic types should not be used as a key in `context.WithValue`.
@@ -286,17 +296,24 @@ _Description_: This rule warns on some common mistakes when using `defer` statem
| name | description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| call-chain | even if deferring call-chains of the form `foo()()` is valid, it does not helps code understanding (only the last call is deferred) |
| call-chain (callChain, callchain) | even if deferring call-chains of the form `foo()()` is valid, it does not helps code understanding (only the last call is deferred) |
| loop | deferring inside loops can be misleading (deferred functions are not executed at the end of the loop iteration but of the current function) and it could lead to exhausting the execution stack |
| method-call | deferring a call to a method can lead to subtle bugs if the method does not have a pointer receiver |
| method-call (methodCall, methodcall) | deferring a call to a method can lead to subtle bugs if the method does not have a pointer receiver |
| recover | calling `recover` outside a deferred function has no effect |
| immediate-recover | calling `recover` at the time a defer is registered, rather than as part of the deferred callback. e.g. `defer recover()` or equivalent. |
| immediate-recover (immediateRecover, immediaterecover) | calling `recover` at the time a defer is registered, rather than as part of the deferred callback. e.g. `defer recover()` or equivalent. |
| return | returning values form a deferred function has no effect |
These gotchas are described [here](https://blog.learngoprogramming.com/gotchas-of-defer-in-go-1-8d070894cb01)
_Configuration_: by default all warnings are enabled but it is possible selectively enable them through configuration. For example to enable only `call-chain` and `loop`:
Examples:
```toml
[rule.defer]
arguments = [["callChain", "loop"]]
```
```toml
[rule.defer]
arguments = [["call-chain", "loop"]]
@@ -310,15 +327,20 @@ More information [here](https://go.dev/wiki/CodeReviewComments#import-dot)
_Configuration_:
* `allowedPackages`: (list of strings) comma-separated list of allowed dot import packages
* `allowedPackages` (`allowedpackages`, `allowed-packages`): (list of strings) comma-separated list of allowed dot import packages
Example:
Examples:
```toml
[rule.dot-imports]
arguments = [{ allowedPackages = ["github.com/onsi/ginkgo/v2","github.com/onsi/gomega"] }]
```
```toml
[rule.dot-imports]
arguments = [{ allowed-packages = ["github.com/onsi/ginkgo/v2","github.com/onsi/gomega"] }]
```
## duplicated-imports
_Description_: It is possible to unintentionally import the same package twice. This rule looks for packages that are imported two or more times.
@@ -348,16 +370,21 @@ if !cond {
_Configuration_: ([]string) rule flags. Available flags are:
* _preserveScope_: do not suggest refactorings that would increase variable scope
* _allowJump_: suggest a new jump (`return`, `continue` or `break` statement) if it could unnest multiple statements. By default, only relocation of _existing_ jumps (i.e. from the `else` clause) are suggested.
* `preserveScope` (`preservescope`, `preserve-scope`): do not suggest refactorings that would increase variable scope
* `allowJump` (`allowjump`, `allow-jump`): suggest a new jump (`return`, `continue` or `break` statement) if it could unnest multiple statements. By default, only relocation of _existing_ jumps (i.e. from the `else` clause) are suggested.
Example:
Examples:
```toml
[rule.early-return]
arguments = ["preserveScope", "allowJump"]
```
```toml
[rule.early-return]
arguments = ["preserve-scope", "allow-jump"]
```
## empty-block
_Description_: Empty blocks make code less readable and could be a symptom of a bug or unfinished refactoring.
@@ -399,8 +426,8 @@ _Configuration (1)_: (string) as a single string, it configures both argument
and return value styles. Accepts 'any', 'short', or 'full' (default: 'any').
_Configuration (2)_: (map[string]any) as a map, allows separate configuration
for function arguments and return values. Valid keys are "funcArgStyle" and
"funcRetValStyle", each accepting 'any', 'short', or 'full'. If a key is not
for function arguments and return values. Valid keys are `funcArgStyle` (`funcargstyle`, `func-arg-style`) and
`funcRetValStyle` (`funcretvalstyle`, `func-ret-val-style`), each accepting 'any', 'short', or 'full'. If a key is not
specified, the default value of 'any' is used.
_Note_: The rule applies checks based on the specified styles. For 'full' style,
@@ -415,13 +442,18 @@ Example (1):
arguments = ["short"]
```
Example (2):
Examples (2):
```toml
[rule.enforce-repeated-arg-type-style]
arguments = [{ funcArgStyle = "full", funcRetValStyle = "short" }]
```
```toml
[rule.enforce-repeated-arg-type-style]
arguments = [{ func-arg-style = "full", func-ret-val-style = "short" }]
```
## enforce-slice-style
_Description_: This rule enforces consistent usage of `make([]type, 0)`, `[]type{}`, or `var []type` for slice initialization.
@@ -487,23 +519,28 @@ _Configuration_: ([]string) rule flags.
Please notice that without configuration, the default behavior of the rule is that of its `golint` counterpart.
Available flags are:
* _checkPrivateReceivers_ enables checking public methods of private types
* _disableStutteringCheck_ disables checking for method names that stutter with the package name (i.e. avoid failure messages of the form _type name will be used as x.XY by other packages, and that stutters; consider calling this Y_)
* _sayRepetitiveInsteadOfStutters_ replaces the use of the term _stutters_ by _repetitive_ in failure messages
* _checkPublicInterface_ enabled checking public method definitions in public interface types
* _disableChecksOnConstants_ disable all checks on constant declarations
* _disableChecksOnFunctions_ disable all checks on function declarations
* _disableChecksOnMethods_ disable all checks on method declarations
* _disableChecksOnTypes_ disable all checks on type declarations
* _disableChecksOnVariables_ disable all checks on variable declarations
* `checkPrivateReceivers` (`checkprivatereceivers`, `check-private-receivers`) enables checking public methods of private types
* `disableStutteringCheck` (`disablestutteringcheck`, `disable-stuttering-check`) disables checking for method names that stutter with the package name (i.e. avoid failure messages of the form _type name will be used as x.XY by other packages, and that stutters; consider calling this Y_)
* `sayRepetitiveInsteadOfStutters` (`sayrepetitiveinsteadofstutters`, `say-repetitive-instead-of-stutters`) replaces the use of the term _stutters_ by _repetitive_ in failure messages
* `checkPublicInterface` (`checkpublicinterface`, `check-public-interface`) enabled checking public method definitions in public interface types
* `disableChecksOnConstants` (`disablechecksonconstants`, `disable-checks-on-constants`) disable all checks on constant declarations
* `disableChecksOnFunctions` (`disablechecksonfunctions`, `disable-checks-on-functions`) disable all checks on function declarations
* `disableChecksOnMethods` (`disablechecksonmethods`, `disable-checks-on-methods`) disable all checks on method declarations
* `disableChecksOnTypes` (`disablechecksontypes`, `disable-checks-on-types`) disable all checks on type declarations
* `disableChecksOnVariables` (`disablechecksonvariables`, `disable-checks-on-variables`) disable all checks on variable declarations
Example:
Examples:
```toml
[rule.exported]
arguments = ["checkPrivateReceivers", "disableStutteringCheck", "checkPublicInterface", "disableChecksOnFunctions"]
```
```toml
[rule.exported]
arguments = ["check-private-receivers", "disable-stuttering-check", "check-public-interface", "disable-checks-on-functions"]
```
## 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.
@@ -523,17 +560,22 @@ _Description_: This rule enforces a maximum number of lines per file, in order t
_Configuration_:
* `max` (int) a maximum number of lines in a file. Must be non-negative integers. 0 means the rule is disabled (default `0`);
* `skipComments` (bool) if true ignore and do not count lines containing just comments (default `false`);
* `skipBlankLines` (bool) if true ignore and do not count lines made up purely of whitespace (default `false`).
* `max`: (int) a maximum number of lines in a file. Must be non-negative integers. 0 means the rule is disabled (default `0`);
* `skipComments` (`skipcomments`, `skip-comments`): (bool) if true ignore and do not count lines containing just comments (default `false`);
* `skipBlankLines` (`skipblanklines`, `skip-blank-lines`): (bool) if true ignore and do not count lines made up purely of whitespace (default `false`).
Example:
Examples:
```toml
[rule.file-length-limit]
arguments = [{max=100,skipComments=true,skipBlankLines=true}]
```
```toml
[rule.file-length-limit]
arguments = [{max=100,skip-comments=true,skip-blank-lines=true}]
```
## filename-format
_Description_: enforces conventions on source file names. By default, the rule enforces filenames of the form `^[_A-Za-z0-9][_A-Za-z0-9-]*\.go$`: Optionally, the rule can be configured to enforce other forms.
@@ -609,8 +651,8 @@ Importantly, aliases with underscores ("_") are always allowed.
_Configuration_ (1): (`string`) as plain string accepts allow regexp pattern for aliases (default: `^[a-z][a-z0-9]{0,}$`).
_Configuration_ (2): (`map[string]string`) as a map accepts two values:
* for a key "allowRegex" accepts allow regexp pattern
* for a key "denyRegex deny regexp pattern
* for a key `allowRegex` (`allowregex`, `allow-regex`) accepts allow regexp pattern
* for a key `denyRegex` (`denyregex`, `deny-regex`) deny regexp pattern
_Note_: If both `allowRegex` and `denyRegex` are provided, the alias must comply with both of them.
If none are given (i.e. an empty map), the default value `^[a-z][a-z0-9]{0,}$` for allowRegex is used.
@@ -623,13 +665,18 @@ Example (1):
arguments = ["^[a-z][a-z0-9]{0,}$"]
```
Example (2):
Examples (2):
```toml
[rule.import-alias-naming]
arguments = [{ allowRegex = "^[a-z][a-z0-9]{0,}$", denyRegex = '^v\d+$' }]
```
```toml
[rule.import-alias-naming]
arguments = [{ allow-regex = "^[a-z][a-z0-9]{0,}$", deny-regex = '^v\d+$' }]
```
## import-shadowing
_Description_: In Go it is possible to declare identifiers (packages, structs,
@@ -667,15 +714,20 @@ More information [here](https://go.dev/wiki/CodeReviewComments#indent-error-flow
_Configuration_: ([]string) rule flags. Available flags are:
* _preserveScope_: do not suggest refactorings that would increase variable scope
* `preserveScope` (`preservescope`, `preserve-scope`): do not suggest refactorings that would increase variable scope
Example:
Examples:
```toml
[rule.indent-error-flow]
arguments = ["preserveScope"]
```
```toml
[rule.indent-error-flow]
arguments = ["preserve-scope"]
```
## line-length-limit
_Description_: Warns in the presence of code lines longer than a configured maximum.
@@ -793,13 +845,20 @@ _Description_: By convention, receiver names in a method should reflect their id
_Configuration_: (optional) list of key-value-pair-map (`[]map[string]any`).
- `maxLength` : (int) max length of receiver name
- `maxLength` (`maxlength`, `max-length`): (int) max length of receiver name
Examples:
```toml
[rule.receiver-naming]
arguments = [{maxLength=2}]
```
```toml
[rule.receiver-naming]
arguments = [{max-length=2}]
```
## 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.
@@ -884,15 +943,20 @@ This rule highlights redundant _else-blocks_ that can be eliminated from the cod
_Configuration_: ([]string) rule flags. Available flags are:
* _preserveScope_: do not suggest refactorings that would increase variable scope
* `preserveScope` (`preservescope`, `preserve-scope`): (string) do not suggest refactorings that would increase variable scope
Example:
Examples:
```toml
[rule.superfluous-else]
arguments = ["preserveScope"]
```
```toml
[rule.superfluous-else]
arguments = ["preserve-scope"]
```
## time-equal
_Description_: This rule warns when using `==` and `!=` for equality check `time.Time` and suggest to `time.time.Equal` method, for about information follow this [link](https://pkg.go.dev/time#Time)
@@ -911,20 +975,25 @@ _Description_: This rule checks whether a type assertion result is checked (the
_Configuration_: list of key-value-pair-map (`[]map[string]any`).
- `acceptIgnoredAssertionResult` : (bool) default `false`, set it to `true` to accept ignored type assertion results like this:
- `acceptIgnoredAssertionResult` (`acceptignoredassertionresult`, `accept-ignored-assertion-result`): (bool) default `false`, set it to `true` to accept ignored type assertion results like this:
```go
foo, _ := bar(.*Baz).
// ^
```
Example:
Examples:
```toml
[rule.unchecked-type-assertion]
arguments = [{acceptIgnoredAssertionResult=true}]
```
```toml
[rule.unchecked-type-assertion]
arguments = [{accept-ignored-assertion-result=true}]
```
## unconditional-recursion
_Description_: Unconditional recursive calls will produce infinite recursion, thus program stack overflow. This rule detects and warns about unconditional (direct) recursive calls.
@@ -972,34 +1041,48 @@ _Configuration_: N/A
_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_: Supports arguments with single of `map[string]any` with option `allowRegex` to provide additional to `_` mask to allowed unused parameter names, for example:
_Configuration_: Supports arguments with single of `map[string]any` with option `allowRegex` (`allowregex`, `allow-regex`) to provide additional to `_` mask to allowed unused parameter names.
Examples:
This allows any names starting with `_`, not just `_` itself:
```go
func SomeFunc(_someObj *MyStruct) {} // matches rule
```
```toml
[rule.unused-parameter]
arguments = [{ allowRegex = "^_" }]
```
allows any names started with `_`, not just `_` itself:
```go
func SomeFunc(_someObj *MyStruct) {} // matches rule
```toml
[rule.unused-parameter]
arguments = [{ allow-regex = "^_" }]
```
## unused-receiver
_Description_: This rule warns on unused method receivers. Methods with unused receivers can be a symptom of an unfinished refactoring or a bug.
_Configuration_: Supports arguments with single of `map[string]any` with option `allowRegex` to provide additional to `_` mask to allowed unused receiver names, for example:
_Configuration_: Supports arguments with single of `map[string]any` with option `allowRegex` to provide additional to `_` mask to allowed unused receiver names.
Examples:
This allows any names starting with `_`, not just `_` itself:
```go
func (_my *MyStruct) SomeMethod() {} // matches rule
```
```toml
[rule.unused-receiver]
arguments = [{ allowRegex = "^_" }]
```
allows any names started with `_`, not just `_` itself:
```go
func (_my *MyStruct) SomeMethod() {} // matches rule
```toml
[rule.unused-receiver]
arguments = [{ allow-regex = "^_" }]
```
## use-any
@@ -1038,26 +1121,32 @@ _Description_: This rule warns when [initialism](https://go.dev/wiki/CodeReviewC
_Configuration_: This rule accepts two slices of strings and one optional slice with single map with named parameters.
(it's due to TOML hasn't "slice of any" and we keep backward compatibility with previous config version)
First slice is an allowlist and second one is a blocklist of initialisms.
In map, you can add "upperCaseConst=true" parameter to allow `UPPER_CASE` for `const`
In map, you can add boolean `upperCaseConst` (`uppercaseconst`, `upper-case-const`) parameter to allow `UPPER_CASE` for `const`
You can also add boolean `skipPackageNameChecks` (`skippackagenamechecks`, `skip-package-name-checks`) to skip package name checks.
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:
Examples:
```toml
[rule.var-naming]
arguments = [["ID"], ["VM"], [{upperCaseConst=true}]]
```
You can also add "skipPackageNameChecks=true" to skip package name checks.
Example:
```toml
[rule.var-naming]
arguments = [[], [], [{skipPackageNameChecks=true}]]
```
```toml
[rule.var-naming]
arguments = [["ID"], ["VM"], [{upper-case-const=true}]]
```
```toml
[rule.var-naming]
arguments = [[], [], [{skip-package-name-checks=true}]]
```
## 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.

View File

@@ -1,15 +1,5 @@
package ifelse
// PreserveScope is a configuration argument that prevents suggestions
// that would enlarge variable scope
const PreserveScope = "preserveScope"
// AllowJump is a configuration argument that permits early-return to
// suggest introducing a new jump (return, continue, etc) statement
// to reduce nesting. By default, suggestions only bring existing jumps
// earlier.
const AllowJump = "allowJump"
// Args contains arguments common to the early-return, indent-error-flow
// and superfluous-else rules
type Args struct {

View File

@@ -10,7 +10,7 @@ import (
// CheckFunc evaluates a rule against the given if-else chain and returns a message
// describing the proposed refactor, along with a indicator of whether such a refactor
// could be found.
type CheckFunc func(Chain, Args) (string, bool)
type CheckFunc func(Chain) (string, bool)
// Apply evaluates the given Rule on if-else chains found within the given AST,
// and returns the failures.
@@ -28,16 +28,9 @@ type CheckFunc func(Chain, Args) (string, bool)
//
// Only the block following "bar" is linted. This is because the rules that use this function
// do not presently have anything to say about earlier blocks in the chain.
func Apply(check CheckFunc, node ast.Node, target Target, args lint.Arguments) []lint.Failure {
func Apply(check CheckFunc, node ast.Node, target Target, args Args) []lint.Failure {
v := &visitor{check: check, target: target}
for _, arg := range args {
switch arg {
case PreserveScope:
v.args.PreserveScope = true
case AllowJump:
v.args.AllowJump = true
}
}
v.args = args
ast.Walk(v, node)
return v.failures
}
@@ -126,7 +119,7 @@ func (v *visitor) visitIf(ifStmt *ast.IfStmt, chain Chain) {
}
func (v *visitor) checkRule(ifStmt *ast.IfStmt, chain Chain) {
msg, found := v.check(chain, v.args)
msg, found := v.check(chain)
if !found {
return // passed the check
}

View File

@@ -212,16 +212,16 @@ func (r *AddConstantRule) Configure(arguments lint.Arguments) error {
}
for k, v := range args {
kind := ""
switch k {
case "allowFloats":
switch {
case isRuleOption(k, "allowFloats"):
kind = kindFLOAT
fallthrough
case "allowInts":
case isRuleOption(k, "allowInts"):
if kind == "" {
kind = kindINT
}
fallthrough
case "allowStrs":
case isRuleOption(k, "allowStrs"):
if kind == "" {
kind = kindSTRING
}
@@ -230,7 +230,7 @@ func (r *AddConstantRule) Configure(arguments lint.Arguments) error {
return fmt.Errorf("invalid argument to the add-constant rule, string expected. Got '%v' (%T)", v, v)
}
r.allowList.add(kind, list)
case "maxLitCount":
case isRuleOption(k, "maxLitCount"):
sl, ok := v.(string)
if !ok {
return fmt.Errorf("invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v' (%T)", v, v)
@@ -241,7 +241,7 @@ func (r *AddConstantRule) Configure(arguments lint.Arguments) error {
return fmt.Errorf("invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v'", v)
}
r.strLitLimit = limit
case "ignoreFuncs":
case isRuleOption(k, "ignoreFuncs"):
excludes, ok := v.(string)
if !ok {
return fmt.Errorf("invalid argument to the ignoreFuncs parameter of add-constant rule, string expected. Got '%v' (%T)", v, v)

193
rule/add_constant_test.go Normal file
View File

@@ -0,0 +1,193 @@
package rule
import (
"errors"
"reflect"
"testing"
"github.com/mgechev/revive/lint"
)
func TestAddConstantRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantList allowList
wantStrLitLimit int
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantList: allowList{
kindINT: {},
kindFLOAT: {},
kindSTRING: {},
},
wantStrLitLimit: 2,
},
{
name: "valid arguments",
arguments: lint.Arguments{
map[string]any{
"allowFloats": "1.0,2.0",
"allowInts": "1,2",
"allowStrs": "a,b",
"maxLitCount": "3",
"ignoreFuncs": "fmt.Println,fmt.Printf",
},
},
wantErr: nil,
wantList: allowList{
kindFLOAT: {"1.0": true, "2.0": true},
kindINT: {"1": true, "2": true},
kindSTRING: {"a": true, "b": true},
},
wantStrLitLimit: 3,
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
map[string]any{
"allowfloats": "1.0,2.0",
"allowints": "1,2",
"allowstrs": "a,b",
"maxlitcount": "3",
"ignorefuncs": "fmt.Println,fmt.Printf",
},
},
wantErr: nil,
wantList: allowList{
kindFLOAT: {"1.0": true, "2.0": true},
kindINT: {"1": true, "2": true},
kindSTRING: {"a": true, "b": true},
},
wantStrLitLimit: 3,
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
map[string]any{
"allow-floats": "1.0,2.0",
"allow-ints": "1,2",
"allow-strs": "a,b",
"max-lit-count": "3",
"ignore-funcs": "fmt.Println,fmt.Printf",
},
},
wantErr: nil,
wantList: allowList{
kindFLOAT: {"1.0": true, "2.0": true},
kindINT: {"1": true, "2": true},
kindSTRING: {"a": true, "b": true},
},
wantStrLitLimit: 3,
},
{
name: "unrecognized key",
arguments: lint.Arguments{
map[string]any{
"unknownKey": "someValue",
},
},
wantErr: nil,
wantList: allowList{
kindINT: {},
kindFLOAT: {},
kindSTRING: {},
},
wantStrLitLimit: 2,
},
{
name: "invalid argument type",
arguments: lint.Arguments{
"invalid_argument",
},
wantErr: errors.New("invalid argument to the add-constant rule, expecting a k,v map. Got string"),
},
{
name: "invalid allowFloats value",
arguments: lint.Arguments{
map[string]any{
"allowFloats": 123,
},
},
wantErr: errors.New("invalid argument to the add-constant rule, string expected. Got '123' (int)"),
},
{
name: "invalid maxLitCount value: not a string",
arguments: lint.Arguments{
map[string]any{
"maxLitCount": 123,
},
},
wantErr: errors.New("invalid argument to the add-constant rule, expecting string representation of an integer. Got '123' (int)"),
},
{
name: "invalid maxLitCount value: not an int",
arguments: lint.Arguments{
map[string]any{
"maxLitCount": "abc",
},
},
wantErr: errors.New("invalid argument to the add-constant rule, expecting string representation of an integer. Got 'abc'"),
},
{
name: "invalid ignoreFuncs value: not a string",
arguments: lint.Arguments{
map[string]any{
"ignoreFuncs": 123,
},
},
wantErr: errors.New("invalid argument to the ignoreFuncs parameter of add-constant rule, string expected. Got '123' (int)"),
},
{
name: "invalid ignoreFuncs value: empty string",
arguments: lint.Arguments{
map[string]any{
"ignoreFuncs": " ",
},
},
wantErr: errors.New("invalid argument to the ignoreFuncs parameter of add-constant rule, expected regular expression must not be empty"),
},
{
name: "invalid ignoreFuncs value: wrong regexp",
arguments: lint.Arguments{
map[string]any{
"ignoreFuncs": "(",
},
},
wantErr: errors.New(`invalid argument to the ignoreFuncs parameter of add-constant rule: regexp "(" does not compile: error parsing regexp: missing closing ): ` + "`(`"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule AddConstantRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if !reflect.DeepEqual(rule.allowList, tt.wantList) {
t.Errorf("unexpected allowList: got = %v, want %v", rule.allowList, tt.wantList)
}
if rule.strLitLimit != tt.wantStrLitLimit {
t.Errorf("unexpected strLitLimit: got = %v, want %v", rule.strLitLimit, tt.wantStrLitLimit)
}
})
}
}

View File

@@ -74,16 +74,14 @@ func (*ContextAsArgumentRule) getAllowTypesFromArguments(args lint.Arguments) (m
return nil, fmt.Errorf("invalid argument to the context-as-argument rule. Expecting a k,v map, got %T", args[0])
}
for k, v := range argKV {
switch k {
case "allowTypesBefore":
if !isRuleOption(k, "allowTypesBefore") {
return nil, fmt.Errorf("invalid argument to the context-as-argument rule. Unrecognized key %s", k)
}
typesBefore, ok := v.(string)
if !ok {
return nil, fmt.Errorf("invalid argument to the context-as-argument.allowTypesBefore rule. Expecting a string, got %T", v)
}
allowTypesBefore = append(allowTypesBefore, strings.Split(typesBefore, ",")...)
default:
return nil, fmt.Errorf("invalid argument to the context-as-argument rule. Unrecognized key %s", k)
}
}
}

View File

@@ -0,0 +1,117 @@
package rule
import (
"errors"
"reflect"
"testing"
"github.com/mgechev/revive/lint"
)
func TestContextAsArgumentRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantTypes map[string]struct{}
}{
{
name: "valid arguments",
arguments: lint.Arguments{
map[string]any{
"allowTypesBefore": "AllowedBeforeType,AllowedBeforeStruct,*AllowedBeforePtrStruct,*testing.T",
},
},
wantErr: nil,
wantTypes: map[string]struct{}{
"context.Context": {},
"AllowedBeforeType": {},
"AllowedBeforeStruct": {},
"*AllowedBeforePtrStruct": {},
"*testing.T": {},
},
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
map[string]any{
"allowtypesbefore": "AllowedBeforeType,AllowedBeforeStruct,*AllowedBeforePtrStruct,*testing.T",
},
},
wantErr: nil,
wantTypes: map[string]struct{}{
"context.Context": {},
"AllowedBeforeType": {},
"AllowedBeforeStruct": {},
"*AllowedBeforePtrStruct": {},
"*testing.T": {},
},
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
map[string]any{
"allow-types-before": "AllowedBeforeType,AllowedBeforeStruct,*AllowedBeforePtrStruct,*testing.T",
},
},
wantErr: nil,
wantTypes: map[string]struct{}{
"context.Context": {},
"AllowedBeforeType": {},
"AllowedBeforeStruct": {},
"*AllowedBeforePtrStruct": {},
"*testing.T": {},
},
},
{
name: "invalid argument type",
arguments: lint.Arguments{
"invalid_argument",
},
wantErr: errors.New("invalid argument to the context-as-argument rule. Expecting a k,v map, got string"),
},
{
name: "invalid allowTypesBefore value",
arguments: lint.Arguments{
map[string]any{
"allowTypesBefore": 123,
},
},
wantErr: errors.New("invalid argument to the context-as-argument.allowTypesBefore rule. Expecting a string, got int"),
},
{
name: "unrecognized key",
arguments: lint.Arguments{
map[string]any{
"unknownKey": "someValue",
},
},
wantErr: errors.New("invalid argument to the context-as-argument rule. Unrecognized key unknownKey"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule ContextAsArgumentRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if !reflect.DeepEqual(rule.allowTypes, tt.wantTypes) {
t.Errorf("unexpected allowTypes: got = %v, want %v", rule.allowTypes, tt.wantTypes)
}
})
}
}

View File

@@ -7,6 +7,15 @@ import (
"github.com/mgechev/revive/lint"
)
var (
deferOptionLoop = normalizeRuleOption("loop")
deferOptionCallChain = normalizeRuleOption("callChain")
deferOptionMethodCall = normalizeRuleOption("methodCall")
deferOptionReturn = normalizeRuleOption("return")
deferOptionRecover = normalizeRuleOption("recover")
deferOptionImmediateRecover = normalizeRuleOption("immediateRecover")
)
// DeferRule lints gotchas in defer statements.
type DeferRule struct {
allow map[string]bool
@@ -45,12 +54,12 @@ func (*DeferRule) Name() string {
func (*DeferRule) allowFromArgs(args lint.Arguments) (map[string]bool, error) {
if len(args) < 1 {
allow := map[string]bool{
"loop": true,
"call-chain": true,
"method-call": true,
"return": true,
"recover": true,
"immediate-recover": true,
deferOptionLoop: true,
deferOptionCallChain: true,
deferOptionMethodCall: true,
deferOptionReturn: true,
deferOptionRecover: true,
deferOptionImmediateRecover: true,
}
return allow, nil
@@ -67,7 +76,7 @@ func (*DeferRule) allowFromArgs(args lint.Arguments) (map[string]bool, error) {
if !ok {
return nil, fmt.Errorf("invalid argument '%v' for 'defer' rule. Expecting string, got %T", subcase, subcase)
}
allow[sc] = true
allow[normalizeRuleOption(sc)] = true
}
return allow, nil
@@ -94,7 +103,7 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor {
return nil
case *ast.ReturnStmt:
if len(n.Results) != 0 && w.inADefer && w.inAFuncLit {
w.newFailure("return in a defer function has no effect", n, 1.0, lint.FailureCategoryLogic, "return")
w.newFailure("return in a defer function has no effect", n, 1.0, lint.FailureCategoryLogic, deferOptionReturn)
}
case *ast.CallExpr:
isCallToRecover := isIdent(n.Fun, "recover")
@@ -103,13 +112,13 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor {
// func fn() { recover() }
//
// confidence is not 1 because recover can be in a function that is deferred elsewhere
w.newFailure("recover must be called inside a deferred function", n, 0.8, lint.FailureCategoryLogic, "recover")
w.newFailure("recover must be called inside a deferred function", n, 0.8, lint.FailureCategoryLogic, deferOptionRecover)
case w.inADefer && !w.inAFuncLit && isCallToRecover:
// defer helper(recover())
//
// confidence is not truly 1 because this could be in a correctly-deferred func,
// but it is very likely to be a misunderstanding of defer's behavior around arguments.
w.newFailure("recover must be called inside a deferred function, this is executing recover immediately", n, 1, lint.FailureCategoryLogic, "immediate-recover")
w.newFailure("recover must be called inside a deferred function, this is executing recover immediately", n, 1, lint.FailureCategoryLogic, deferOptionImmediateRecover)
}
return nil // no need to analyze the arguments of the function call
case *ast.DeferStmt:
@@ -118,7 +127,7 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor {
//
// confidence is not truly 1 because this could be in a correctly-deferred func,
// but normally this doesn't suppress a panic, and even if it did it would silently discard the value.
w.newFailure("recover must be called inside a deferred function, this is executing recover immediately", n, 1, lint.FailureCategoryLogic, "immediate-recover")
w.newFailure("recover must be called inside a deferred function, this is executing recover immediately", n, 1, lint.FailureCategoryLogic, deferOptionImmediateRecover)
}
w.visitSubtree(n.Call.Fun, true, false, false)
for _, a := range n.Call.Args {
@@ -131,17 +140,17 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor {
}
if w.inALoop {
w.newFailure("prefer not to defer inside loops", n, 1.0, lint.FailureCategoryBadPractice, "loop")
w.newFailure("prefer not to defer inside loops", n, 1.0, lint.FailureCategoryBadPractice, deferOptionLoop)
}
switch fn := n.Call.Fun.(type) {
case *ast.CallExpr:
w.newFailure("prefer not to defer chains of function calls", fn, 1.0, lint.FailureCategoryBadPractice, "call-chain")
w.newFailure("prefer not to defer chains of function calls", fn, 1.0, lint.FailureCategoryBadPractice, deferOptionCallChain)
case *ast.SelectorExpr:
if id, ok := fn.X.(*ast.Ident); ok {
isMethodCall := id != nil && id.Obj != nil && id.Obj.Kind == ast.Typ
if isMethodCall {
w.newFailure("be careful when deferring calls to methods without pointer receiver", fn, 0.8, lint.FailureCategoryBadPractice, "method-call")
w.newFailure("be careful when deferring calls to methods without pointer receiver", fn, 0.8, lint.FailureCategoryBadPractice, deferOptionMethodCall)
}
}
}

137
rule/defer_test.go Normal file
View File

@@ -0,0 +1,137 @@
package rule
import (
"errors"
"reflect"
"testing"
"github.com/mgechev/revive/lint"
)
func TestDeferRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantAllow map[string]bool
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantAllow: map[string]bool{
"loop": true,
"callchain": true,
"methodcall": true,
"return": true,
"recover": true,
"immediaterecover": true,
},
},
{
name: "valid arguments",
arguments: lint.Arguments{
[]any{
"loop",
"callChain",
"methodCall",
"return",
"recover",
"immediateRecover",
},
},
wantErr: nil,
wantAllow: map[string]bool{
"loop": true,
"callchain": true,
"methodcall": true,
"return": true,
"recover": true,
"immediaterecover": true,
},
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
[]any{
"loop",
"callchain",
"methodcall",
"return",
"recover",
"immediaterecover",
},
},
wantErr: nil,
wantAllow: map[string]bool{
"loop": true,
"callchain": true,
"methodcall": true,
"return": true,
"recover": true,
"immediaterecover": true,
},
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
[]any{
"loop",
"call-chain",
"method-call",
"return",
"recover",
"immediate-recover",
},
},
wantErr: nil,
wantAllow: map[string]bool{
"loop": true,
"callchain": true,
"methodcall": true,
"return": true,
"recover": true,
"immediaterecover": true,
},
},
{
name: "invalid argument type",
arguments: lint.Arguments{
"invalid_argument",
},
wantErr: errors.New("invalid argument 'invalid_argument' for 'defer' rule. Expecting []string, got string"),
},
{
name: "invalid subcase type",
arguments: lint.Arguments{
[]any{123},
},
wantErr: errors.New("invalid argument '123' for 'defer' rule. Expecting string, got int"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule DeferRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if !reflect.DeepEqual(rule.allow, tt.wantAllow) {
t.Errorf("unexpected allow: got = %v, want %v", rule.allow, tt.wantAllow)
}
})
}
}

View File

@@ -50,10 +50,13 @@ func (r *DotImportsRule) Configure(arguments lint.Arguments) error {
return fmt.Errorf("invalid argument to the dot-imports rule. Expecting a k,v map, got %T", arguments[0])
}
if allowedPkgArg, ok := args["allowedPackages"]; ok {
pkgs, ok := allowedPkgArg.([]any)
for k, v := range args {
if !isRuleOption(k, "allowedPackages") {
continue
}
pkgs, ok := v.([]any)
if !ok {
return fmt.Errorf("invalid argument to the dot-imports rule, []string expected. Got '%v' (%T)", allowedPkgArg, allowedPkgArg)
return fmt.Errorf("invalid argument to the dot-imports rule, []string expected. Got '%v' (%T)", v, v)
}
for _, p := range pkgs {
pkg, ok := p.(string)

127
rule/dot_imports_test.go Normal file
View File

@@ -0,0 +1,127 @@
package rule
import (
"errors"
"reflect"
"testing"
"github.com/mgechev/revive/lint"
)
func TestDotImportsRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantAllowPackages allowPackages
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantAllowPackages: allowPackages{},
},
{
name: "no allowedPackages key",
arguments: lint.Arguments{
map[string]any{
"invalid": "argument",
},
},
wantErr: nil,
wantAllowPackages: allowPackages{},
},
{
name: "valid arguments",
arguments: lint.Arguments{
map[string]any{
"allowedPackages": []any{
"github.com/onsi/ginkgo/v2",
},
},
},
wantErr: nil,
wantAllowPackages: allowPackages{
`"github.com/onsi/ginkgo/v2"`: struct{}{},
},
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
map[string]any{
"allowedpackages": []any{
"github.com/onsi/ginkgo/v2",
},
},
},
wantErr: nil,
wantAllowPackages: allowPackages{
`"github.com/onsi/ginkgo/v2"`: struct{}{},
},
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
map[string]any{
"allowed-packages": []any{
"github.com/onsi/ginkgo/v2",
},
},
},
wantErr: nil,
wantAllowPackages: allowPackages{
`"github.com/onsi/ginkgo/v2"`: struct{}{},
},
},
{
name: "invalid argument type",
arguments: lint.Arguments{
"invalid_argument",
},
wantErr: errors.New("invalid argument to the dot-imports rule. Expecting a k,v map, got string"),
},
{
name: "invalid allowedPackages type",
arguments: lint.Arguments{
map[string]any{
"allowedPackages": "invalid",
},
},
wantErr: errors.New("invalid argument to the dot-imports rule, []string expected. Got 'invalid' (string)"),
},
{
name: "invalid allowedPackages value type",
arguments: lint.Arguments{
map[string]any{
"allowedPackages": []any{123},
},
},
wantErr: errors.New("invalid argument to the dot-imports rule, string expected. Got '123' (int)"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule DotImportsRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if !reflect.DeepEqual(rule.allowedPackages, tt.wantAllowPackages) {
t.Errorf("unexpected allowedPackages: got = %v, want %v", rule.allowedPackages, tt.wantAllowPackages)
}
})
}
}

View File

@@ -9,11 +9,40 @@ import (
// EarlyReturnRule finds opportunities to reduce nesting by inverting
// the condition of an "if" block.
type EarlyReturnRule struct{}
type EarlyReturnRule struct {
// preserveScope prevents suggestions that would enlarge variable scope.
preserveScope bool
// allowJump permits early-return to suggest introducing a new jump
// (return, continue, etc) statement to reduce nesting.
// By default, suggestions only bring existing jumps earlier.
allowJump bool
}
// Configure validates the rule configuration, and configures the rule accordingly.
//
// Configuration implements the [lint.ConfigurableRule] interface.
func (e *EarlyReturnRule) Configure(arguments lint.Arguments) error {
for _, arg := range arguments {
sarg, ok := arg.(string)
if !ok {
continue
}
switch {
case isRuleOption(sarg, "preserveScope"):
e.preserveScope = true
case isRuleOption(sarg, "allowJump"):
e.allowJump = true
}
}
return nil
}
// Apply applies the rule to given file.
func (e *EarlyReturnRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
return ifelse.Apply(e.checkIfElse, file.AST, ifelse.TargetIf, args)
func (e *EarlyReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
return ifelse.Apply(e.checkIfElse, file.AST, ifelse.TargetIf, ifelse.Args{
PreserveScope: e.preserveScope,
AllowJump: e.allowJump,
})
}
// Name returns the rule name.
@@ -21,13 +50,13 @@ func (*EarlyReturnRule) Name() string {
return "early-return"
}
func (*EarlyReturnRule) checkIfElse(chain ifelse.Chain, args ifelse.Args) (string, bool) {
func (e *EarlyReturnRule) checkIfElse(chain ifelse.Chain) (string, bool) {
if chain.HasElse {
if !chain.Else.BranchKind.Deviates() {
// this rule only applies if the else-block deviates control flow
return "", false
}
} else if !args.AllowJump || !chain.AtBlockEnd || !chain.BlockEndKind.Deviates() || chain.If.IsShort() {
} else if !e.allowJump || !chain.AtBlockEnd || !chain.BlockEndKind.Deviates() || chain.If.IsShort() {
// this kind of refactor requires introducing a new indented "return", "continue" or "break" statement,
// so ignore unless we are able to outdent multiple statements in exchange.
return "", false
@@ -44,7 +73,7 @@ func (*EarlyReturnRule) checkIfElse(chain ifelse.Chain, args ifelse.Args) (strin
return "", false
}
if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.If.HasDecls()) {
if e.preserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.If.HasDecls()) {
// avoid increasing variable scope
return "", false
}

92
rule/early_return_test.go Normal file
View File

@@ -0,0 +1,92 @@
package rule
import (
"testing"
"github.com/mgechev/revive/lint"
)
func TestEarlyReturn_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantPreserveScope bool
wantAllowJump bool
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantPreserveScope: false,
wantAllowJump: false,
},
{
name: "valid arguments",
arguments: lint.Arguments{
"preserveScope",
"allowJump",
},
wantErr: nil,
wantPreserveScope: true,
wantAllowJump: true,
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
"preservescope",
"allowjump",
},
wantErr: nil,
wantPreserveScope: true,
wantAllowJump: true,
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
"preserve-scope",
"allow-jump",
},
wantErr: nil,
wantPreserveScope: true,
wantAllowJump: true,
},
{
name: "invalid arguments",
arguments: lint.Arguments{
"unknown",
},
wantErr: nil,
wantPreserveScope: false,
wantAllowJump: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule EarlyReturnRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if rule.preserveScope != tt.wantPreserveScope {
t.Errorf("unexpected preserveScope: got = %v, want = %v", rule.preserveScope, tt.wantPreserveScope)
}
if rule.allowJump != tt.wantAllowJump {
t.Errorf("unexpected allowJump: got = %v, want = %v", rule.allowJump, tt.wantAllowJump)
}
})
}
}

View File

@@ -69,8 +69,8 @@ func (r *EnforceRepeatedArgTypeStyleRule) Configure(arguments lint.Arguments) er
r.funcRetValStyle = valstyle
case map[string]any: // expecting map[string]string
for k, v := range funcArgStyle {
switch k {
case "funcArgStyle":
switch {
case isRuleOption(k, "funcArgStyle"):
val, ok := v.(string)
if !ok {
return fmt.Errorf("invalid map value type for 'enforce-repeated-arg-type-style' rule. Expecting string, got %T", v)
@@ -80,7 +80,7 @@ func (r *EnforceRepeatedArgTypeStyleRule) Configure(arguments lint.Arguments) er
return err
}
r.funcArgStyle = valstyle
case "funcRetValStyle":
case isRuleOption(k, "funcRetValStyle"):
val, ok := v.(string)
if !ok {
return fmt.Errorf("invalid map value '%v' for 'enforce-repeated-arg-type-style' rule. Expecting string, got %T", v, v)

View File

@@ -0,0 +1,158 @@
package rule
import (
"errors"
"testing"
"github.com/mgechev/revive/lint"
)
func TestEnforceRepeatedArgTypeStyleRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantFuncArgStyle enforceRepeatedArgTypeStyleType
wantFuncRetValStyle enforceRepeatedArgTypeStyleType
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantFuncArgStyle: "any",
wantFuncRetValStyle: "any",
},
{
name: "valid arguments: short",
arguments: lint.Arguments{
"short",
},
wantErr: nil,
wantFuncArgStyle: "short",
wantFuncRetValStyle: "short",
},
{
name: "valid arguments",
arguments: lint.Arguments{
map[string]any{
"funcArgStyle": "full",
"funcRetValStyle": "short",
},
},
wantErr: nil,
wantFuncArgStyle: "full",
wantFuncRetValStyle: "short",
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
map[string]any{
"funcargstyle": "full",
"funcretvalstyle": "short",
},
},
wantErr: nil,
wantFuncArgStyle: "full",
wantFuncRetValStyle: "short",
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
map[string]any{
"func-arg-style": "full",
"func-ret-val-style": "short",
},
},
wantErr: nil,
wantFuncArgStyle: "full",
wantFuncRetValStyle: "short",
},
{
name: "unrecognized key",
arguments: lint.Arguments{
map[string]any{
"unknownKey": "someValue",
},
},
wantErr: errors.New("invalid map key for 'enforce-repeated-arg-type-style' rule. Expecting 'funcArgStyle' or 'funcRetValStyle', got unknownKey"),
},
{
name: "invalid argument type",
arguments: lint.Arguments{
123,
},
wantErr: errors.New("invalid argument '123' for 'import-alias-naming' rule. Expecting string or map[string]string, got int"),
},
{
name: "invalid argument when string",
arguments: lint.Arguments{
"invalid_argument",
},
wantErr: errors.New("invalid argument to the enforce-repeated-arg-type-style rule: invalid repeated arg type style: invalid_argument (expecting one of [any short full])"),
},
{
name: "invalid funcArgStyle value",
arguments: lint.Arguments{
map[string]any{
"funcArgStyle": 123,
},
},
wantErr: errors.New("invalid map value type for 'enforce-repeated-arg-type-style' rule. Expecting string, got int"),
},
{
name: "invalid funcRetValStyle value",
arguments: lint.Arguments{
map[string]any{
"funcRetValStyle": 123,
},
},
wantErr: errors.New("invalid map value '123' for 'enforce-repeated-arg-type-style' rule. Expecting string, got int"),
},
{
name: "invalid funcArgStyle value: wrong string",
arguments: lint.Arguments{
map[string]any{
"funcArgStyle": "invalid",
},
},
wantErr: errors.New("invalid argument to the enforce-repeated-arg-type-style rule: invalid repeated arg type style: invalid (expecting one of [any short full])"),
},
{
name: "invalid funcRetValStyle value: wrong string",
arguments: lint.Arguments{
map[string]any{
"funcRetValStyle": "invalid",
},
},
wantErr: errors.New("invalid argument to the enforce-repeated-arg-type-style rule: invalid repeated arg type style: invalid (expecting one of [any short full])"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule EnforceRepeatedArgTypeStyleRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if rule.funcArgStyle != tt.wantFuncArgStyle {
t.Errorf("unexpected funcArgStyle: got = %v, want %v", rule.funcArgStyle, tt.wantFuncArgStyle)
}
if rule.funcRetValStyle != tt.wantFuncRetValStyle {
t.Errorf("unexpected funcRetValStyle: got = %v, want %v", rule.funcRetValStyle, tt.wantFuncRetValStyle)
}
})
}
}

View File

@@ -78,24 +78,24 @@ func (r *ExportedRule) Configure(arguments lint.Arguments) error {
for _, flag := range arguments {
switch flag := flag.(type) {
case string:
switch flag {
case "checkPrivateReceivers":
switch {
case isRuleOption(flag, "checkPrivateReceivers"):
r.disabledChecks.PrivateReceivers = false
case "disableStutteringCheck":
case isRuleOption(flag, "disableStutteringCheck"):
r.disabledChecks.Stuttering = true
case "sayRepetitiveInsteadOfStutters":
case isRuleOption(flag, "sayRepetitiveInsteadOfStutters"):
r.stuttersMsg = "is repetitive"
case "checkPublicInterface":
case isRuleOption(flag, "checkPublicInterface"):
r.disabledChecks.PublicInterfaces = false
case "disableChecksOnConstants":
case isRuleOption(flag, "disableChecksOnConstants"):
r.disabledChecks.Const = true
case "disableChecksOnFunctions":
case isRuleOption(flag, "disableChecksOnFunctions"):
r.disabledChecks.Function = true
case "disableChecksOnMethods":
case isRuleOption(flag, "disableChecksOnMethods"):
r.disabledChecks.Method = true
case "disableChecksOnTypes":
case isRuleOption(flag, "disableChecksOnTypes"):
r.disabledChecks.Type = true
case "disableChecksOnVariables":
case isRuleOption(flag, "disableChecksOnVariables"):
r.disabledChecks.Var = true
default:
return fmt.Errorf("unknown configuration flag %s for %s rule", flag, r.Name())

178
rule/exported_test.go Normal file
View File

@@ -0,0 +1,178 @@
package rule
import (
"errors"
"testing"
"github.com/mgechev/revive/lint"
)
func TestExportedRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantDisabledChecks disabledChecks
wantStuttersMsg string
}{
{
name: "default configuration",
arguments: lint.Arguments{},
wantErr: nil,
wantDisabledChecks: disabledChecks{
PrivateReceivers: true,
PublicInterfaces: true,
},
wantStuttersMsg: "stutters",
},
{
name: "valid arguments",
arguments: lint.Arguments{
"checkPrivateReceivers",
"disableStutteringCheck",
"checkPublicInterface",
"disableChecksOnConstants",
"disableChecksOnFunctions",
"disableChecksOnMethods",
"disableChecksOnTypes",
"disableChecksOnVariables",
},
wantErr: nil,
wantDisabledChecks: disabledChecks{
PrivateReceivers: false,
PublicInterfaces: false,
Const: true,
Function: true,
Method: true,
Stuttering: true,
Type: true,
Var: true,
},
wantStuttersMsg: "stutters",
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
"checkprivatereceivers",
"disablestutteringcheck",
"checkpublicinterface",
"disablechecksonconstants",
"disablechecksonfunctions",
"disablechecksonmethods",
"disablechecksontypes",
"disablechecksonvariables",
},
wantErr: nil,
wantDisabledChecks: disabledChecks{
PrivateReceivers: false,
PublicInterfaces: false,
Const: true,
Function: true,
Method: true,
Stuttering: true,
Type: true,
Var: true,
},
wantStuttersMsg: "stutters",
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
"check-private-receivers",
"disable-stuttering-check",
"check-public-interface",
"disable-checks-on-constants",
"disable-checks-on-functions",
"disable-checks-on-methods",
"disable-checks-on-types",
"disable-checks-on-variables",
},
wantErr: nil,
wantDisabledChecks: disabledChecks{
PrivateReceivers: false,
PublicInterfaces: false,
Const: true,
Function: true,
Method: true,
Stuttering: true,
Type: true,
Var: true,
},
wantStuttersMsg: "stutters",
},
{
name: "valid sayRepetitiveInsteadOfStutters",
arguments: lint.Arguments{
"sayRepetitiveInsteadOfStutters",
},
wantErr: nil,
wantDisabledChecks: disabledChecks{
PrivateReceivers: true,
PublicInterfaces: true,
},
wantStuttersMsg: "is repetitive",
},
{
name: "valid lowercased sayRepetitiveInsteadOfStutters",
arguments: lint.Arguments{
"sayrepetitiveinsteadofstutters",
},
wantErr: nil,
wantDisabledChecks: disabledChecks{
PrivateReceivers: true,
PublicInterfaces: true,
},
wantStuttersMsg: "is repetitive",
},
{
name: "valid kebab-cased sayRepetitiveInsteadOfStutters",
arguments: lint.Arguments{
"say-repetitive-instead-of-stutters",
},
wantErr: nil,
wantDisabledChecks: disabledChecks{
PrivateReceivers: true,
PublicInterfaces: true,
},
wantStuttersMsg: "is repetitive",
},
{
name: "unknown configuration flag",
arguments: lint.Arguments{"unknownFlag"},
wantErr: errors.New("unknown configuration flag unknownFlag for exported rule"),
},
{
name: "invalid argument type",
arguments: lint.Arguments{123},
wantErr: errors.New("invalid argument for the exported rule: expecting a string, got int"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule ExportedRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if rule.disabledChecks != tt.wantDisabledChecks {
t.Errorf("unexpected disabledChecks: got = %+v, want %+v", rule.disabledChecks, tt.wantDisabledChecks)
}
if rule.stuttersMsg != tt.wantStuttersMsg {
t.Errorf("unexpected stuttersMsg: got = %v, want %v", rule.stuttersMsg, tt.wantStuttersMsg)
}
})
}
}

View File

@@ -83,23 +83,23 @@ func (r *FileLengthLimitRule) Configure(arguments lint.Arguments) error {
return fmt.Errorf(`invalid argument to the "file-length-limit" rule. Expecting a k,v map, got %T`, arguments[0])
}
for k, v := range argKV {
switch k {
case "max":
switch {
case isRuleOption(k, "max"):
maxLines, ok := v.(int64)
if !ok || maxLines < 0 {
return fmt.Errorf(`invalid configuration value for max lines in "file-length-limit" rule; need positive int64 but got %T`, arguments[0])
return fmt.Errorf(`invalid configuration value for max lines in "file-length-limit" rule; need positive int64 but got %T`, v)
}
r.max = int(maxLines)
case "skipComments":
case isRuleOption(k, "skipComments"):
skipComments, ok := v.(bool)
if !ok {
return fmt.Errorf(`invalid configuration value for skip comments in "file-length-limit" rule; need bool but got %T`, arguments[1])
return fmt.Errorf(`invalid configuration value for skip comments in "file-length-limit" rule; need bool but got %T`, v)
}
r.skipComments = skipComments
case "skipBlankLines":
case isRuleOption(k, "skipBlankLines"):
skipBlankLines, ok := v.(bool)
if !ok {
return fmt.Errorf(`invalid configuration value for skip blank lines in "file-length-limit" rule; need bool but got %T`, arguments[2])
return fmt.Errorf(`invalid configuration value for skip blank lines in "file-length-limit" rule; need bool but got %T`, v)
}
r.skipBlankLines = skipBlankLines
}

View File

@@ -0,0 +1,121 @@
package rule
import (
"errors"
"testing"
"github.com/mgechev/revive/lint"
)
func TestFileLengthLimitRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantMax int
wantSkipComments bool
wantSkipBlankLines bool
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantMax: 0,
wantSkipComments: false,
wantSkipBlankLines: false,
},
{
name: "valid arguments",
arguments: lint.Arguments{map[string]any{
"max": int64(100),
"skipComments": true,
"skipBlankLines": true,
}},
wantErr: nil,
wantMax: 100,
wantSkipComments: true,
wantSkipBlankLines: true,
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{map[string]any{
"max": int64(100),
"skipcomments": true,
"skipblanklines": true,
}},
wantErr: nil,
wantMax: 100,
wantSkipComments: true,
wantSkipBlankLines: true,
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{map[string]any{
"max": int64(100),
"skip-comments": true,
"skip-blank-lines": true,
}},
wantErr: nil,
wantMax: 100,
wantSkipComments: true,
wantSkipBlankLines: true,
},
{
name: "invalid argument",
arguments: lint.Arguments{123},
wantErr: errors.New(`invalid argument to the "file-length-limit" rule. Expecting a k,v map, got int`),
},
{
name: "invalid max type",
arguments: lint.Arguments{map[string]any{
"max": "invalid",
}},
wantErr: errors.New(`invalid configuration value for max lines in "file-length-limit" rule; need positive int64 but got string`),
},
{
name: "invalid skipComments type",
arguments: lint.Arguments{map[string]any{
"skipComments": "invalid",
}},
wantErr: errors.New(`invalid configuration value for skip comments in "file-length-limit" rule; need bool but got string`),
},
{
name: "invalid skipBlankLines type",
arguments: lint.Arguments{map[string]any{
"skipBlankLines": "invalid",
}},
wantErr: errors.New(`invalid configuration value for skip blank lines in "file-length-limit" rule; need bool but got string`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule FileLengthLimitRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if rule.max != tt.wantMax {
t.Errorf("unexpected max: got = %v, want %v", rule.max, tt.wantMax)
}
if rule.skipComments != tt.wantSkipComments {
t.Errorf("unexpected skipComments: got = %v, want %v", rule.skipComments, tt.wantSkipComments)
}
if rule.skipBlankLines != tt.wantSkipBlankLines {
t.Errorf("unexpected skipBlankLines: got = %v, want %v", rule.skipBlankLines, tt.wantSkipBlankLines)
}
})
}
}

View File

@@ -34,13 +34,13 @@ func (r *ImportAliasNamingRule) Configure(arguments lint.Arguments) error {
}
case map[string]any: // expecting map[string]string
for k, v := range namingRule {
switch k {
case "allowRegex":
switch {
case isRuleOption(k, "allowRegex"):
err := r.setAllowRule(v)
if err != nil {
return err
}
case "denyRegex":
case isRuleOption(k, "denyRegex"):
err := r.setDenyRule(v)
if err != nil {
return err

View File

@@ -0,0 +1,133 @@
package rule
import (
"errors"
"reflect"
"regexp"
"testing"
"github.com/mgechev/revive/lint"
)
func TestImportAliasNamingRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantAllowRegex *regexp.Regexp
wantDenyRegex *regexp.Regexp
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantAllowRegex: regexp.MustCompile("^[a-z][a-z0-9]{0,}$"),
wantDenyRegex: nil,
},
{
name: "valid string argument",
arguments: lint.Arguments{"^[a-z][a-z0-9]*$"},
wantErr: nil,
wantAllowRegex: regexp.MustCompile("^[a-z][a-z0-9]*$"),
wantDenyRegex: nil,
},
{
name: "valid map arguments",
arguments: lint.Arguments{map[string]any{
"allowRegex": "^[a-z][a-z0-9]*$",
"denyRegex": "^v\\d+$",
}},
wantErr: nil,
wantAllowRegex: regexp.MustCompile("^[a-z][a-z0-9]*$"),
wantDenyRegex: regexp.MustCompile("^v\\d+$"),
},
{
name: "valid map lowercased arguments",
arguments: lint.Arguments{map[string]any{
"allowregex": "^[a-z][a-z0-9]*$",
"denyregex": "^v\\d+$",
}},
wantErr: nil,
wantAllowRegex: regexp.MustCompile("^[a-z][a-z0-9]*$"),
wantDenyRegex: regexp.MustCompile("^v\\d+$"),
},
{
name: "valid map kebab-cased arguments",
arguments: lint.Arguments{map[string]any{
"allow-regex": "^[a-z][a-z0-9]*$",
"deny-regex": "^v\\d+$",
}},
wantErr: nil,
wantAllowRegex: regexp.MustCompile("^[a-z][a-z0-9]*$"),
wantDenyRegex: regexp.MustCompile("^v\\d+$"),
},
{
name: "invalid argument type",
arguments: lint.Arguments{123},
wantErr: errors.New(`invalid argument '123' for 'import-alias-naming' rule. Expecting string or map[string]string, got int`),
},
{
name: "invalid string argument regex",
arguments: lint.Arguments{"["},
wantErr: errors.New("invalid argument to the import-alias-naming allowRegexp rule. Expecting \"[\" to be a valid regular expression, got: error parsing regexp: missing closing ]: `[`"),
},
{
name: "invalid map key",
arguments: lint.Arguments{
map[string]any{
"unknownKey": "value",
},
},
wantErr: errors.New(`invalid map key for 'import-alias-naming' rule. Expecting 'allowRegex' or 'denyRegex', got unknownKey`),
},
{
name: "invalid allowRegex type",
arguments: lint.Arguments{map[string]any{
"allowRegex": 123,
}},
wantErr: errors.New("invalid argument '123' for import-alias-naming allowRegexp rule. Expecting string, got int"),
},
{
name: "invalid denyRegex type",
arguments: lint.Arguments{map[string]any{
"denyRegex": 123,
}},
wantErr: errors.New("invalid argument '123' for import-alias-naming denyRegexp rule. Expecting string, got int"),
},
{
name: "invalid denyRegex regex",
arguments: lint.Arguments{map[string]any{
"denyRegex": "[",
}},
wantErr: errors.New(`invalid argument to the import-alias-naming denyRegexp rule. Expecting "[" to be a valid regular expression, got: error parsing regexp: missing closing ]: ` + "`[`"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule ImportAliasNamingRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if !reflect.DeepEqual(rule.allowRegexp, tt.wantAllowRegex) {
t.Errorf("unexpected allowRegex: got = %v, want %v", rule.allowRegexp, tt.wantAllowRegex)
}
if !reflect.DeepEqual(rule.denyRegexp, tt.wantDenyRegex) {
t.Errorf("unexpected denyRegexp: got = %v, want %v", rule.denyRegexp, tt.wantDenyRegex)
}
})
}
}

View File

@@ -6,11 +6,33 @@ import (
)
// IndentErrorFlowRule prevents redundant else statements.
type IndentErrorFlowRule struct{}
type IndentErrorFlowRule struct {
// preserveScope prevents suggestions that would enlarge variable scope.
preserveScope bool
}
// Configure validates the rule configuration, and configures the rule accordingly.
//
// Configuration implements the [lint.ConfigurableRule] interface.
func (e *IndentErrorFlowRule) Configure(arguments lint.Arguments) error {
for _, arg := range arguments {
sarg, ok := arg.(string)
if !ok {
continue
}
if isRuleOption(sarg, "preserveScope") {
e.preserveScope = true
}
}
return nil
}
// Apply applies the rule to given file.
func (e *IndentErrorFlowRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
return ifelse.Apply(e.checkIfElse, file.AST, ifelse.TargetElse, args)
func (e *IndentErrorFlowRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
return ifelse.Apply(e.checkIfElse, file.AST, ifelse.TargetElse, ifelse.Args{
PreserveScope: e.preserveScope,
// AllowJump is not used by this rule
})
}
// Name returns the rule name.
@@ -18,7 +40,7 @@ func (*IndentErrorFlowRule) Name() string {
return "indent-error-flow"
}
func (*IndentErrorFlowRule) checkIfElse(chain ifelse.Chain, args ifelse.Args) (string, bool) {
func (e *IndentErrorFlowRule) checkIfElse(chain ifelse.Chain) (string, bool) {
if !chain.HasElse {
return "", false
}
@@ -39,7 +61,7 @@ func (*IndentErrorFlowRule) checkIfElse(chain ifelse.Chain, args ifelse.Args) (s
return "", false
}
if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.Else.HasDecls()) {
if e.preserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.Else.HasDecls()) {
// avoid increasing variable scope
return "", false
}

View File

@@ -0,0 +1,80 @@
package rule
import (
"testing"
"github.com/mgechev/revive/lint"
)
func TestIndentErrorFlowRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantPreserveScope bool
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantPreserveScope: false,
},
{
name: "valid arguments",
arguments: lint.Arguments{
"preserveScope",
},
wantErr: nil,
wantPreserveScope: true,
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
"preservescope",
},
wantErr: nil,
wantPreserveScope: true,
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
"preserve-scope",
},
wantErr: nil,
wantPreserveScope: true,
},
{
name: "invalid arguments",
arguments: lint.Arguments{
"unknown",
},
wantErr: nil,
wantPreserveScope: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule IndentErrorFlowRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if rule.preserveScope != tt.wantPreserveScope {
t.Errorf("unexpected preserveScope: got = %v, want = %v", rule.preserveScope, tt.wantPreserveScope)
}
})
}
}

View File

@@ -29,16 +29,14 @@ func (r *ReceiverNamingRule) Configure(arguments lint.Arguments) error {
}
for k, v := range args {
switch k {
case "maxLength":
if !isRuleOption(k, "maxLength") {
return fmt.Errorf("unknown argument %s for %s rule", k, r.Name())
}
value, ok := v.(int64)
if !ok {
return fmt.Errorf("invalid value %v for argument %s of rule %s, expected integer value got %T", v, k, r.Name(), v)
}
r.receiverNameMaxLength = int(value)
default:
return fmt.Errorf("unknown argument %s for %s rule", k, r.Name())
}
}
return nil
}

View File

@@ -0,0 +1,92 @@
package rule
import (
"errors"
"testing"
"github.com/mgechev/revive/lint"
)
func TestReceiverNamingRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantMaxLength int
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantMaxLength: -1,
},
{
name: "invalid type",
arguments: lint.Arguments{123},
wantErr: errors.New("unable to get arguments for rule receiver-naming. Expected object of key-value-pairs"),
},
{
name: "valid maxLength argument",
arguments: lint.Arguments{map[string]any{
"maxLength": int64(10),
}},
wantErr: nil,
wantMaxLength: 10,
},
{
name: "valid lowercased argument",
arguments: lint.Arguments{map[string]any{
"maxlength": int64(10),
}},
wantErr: nil,
wantMaxLength: 10,
},
{
name: "valid kebab-cased argument",
arguments: lint.Arguments{map[string]any{
"max-length": int64(10),
}},
wantErr: nil,
wantMaxLength: 10,
},
{
name: "invalid maxLength type",
arguments: lint.Arguments{map[string]any{
"maxLength": "10",
}},
wantErr: errors.New("invalid value 10 for argument maxLength of rule receiver-naming, expected integer value got string"),
},
{
name: "unknown argument",
arguments: lint.Arguments{map[string]any{
"unknownKey": 10,
}},
wantErr: errors.New("unknown argument unknownKey for receiver-naming rule"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule ReceiverNamingRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if rule.receiverNameMaxLength != tt.wantMaxLength {
t.Errorf("unexpected maxLength: got = %v, want %v", rule.receiverNameMaxLength, tt.wantMaxLength)
}
})
}
}

View File

@@ -8,11 +8,33 @@ import (
)
// SuperfluousElseRule lints given else constructs.
type SuperfluousElseRule struct{}
type SuperfluousElseRule struct {
// preserveScope prevents suggestions that would enlarge variable scope.
preserveScope bool
}
// Configure validates the rule configuration, and configures the rule accordingly.
//
// Configuration implements the [lint.ConfigurableRule] interface.
func (e *SuperfluousElseRule) Configure(arguments lint.Arguments) error {
for _, arg := range arguments {
sarg, ok := arg.(string)
if !ok {
continue
}
if isRuleOption(sarg, "preserveScope") {
e.preserveScope = true
}
}
return nil
}
// Apply applies the rule to given file.
func (e *SuperfluousElseRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
return ifelse.Apply(e.checkIfElse, file.AST, ifelse.TargetElse, args)
func (e *SuperfluousElseRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
return ifelse.Apply(e.checkIfElse, file.AST, ifelse.TargetElse, ifelse.Args{
PreserveScope: e.preserveScope,
// AllowJump is not used by this rule
})
}
// Name returns the rule name.
@@ -20,7 +42,7 @@ func (*SuperfluousElseRule) Name() string {
return "superfluous-else"
}
func (*SuperfluousElseRule) checkIfElse(chain ifelse.Chain, args ifelse.Args) (string, bool) {
func (e *SuperfluousElseRule) checkIfElse(chain ifelse.Chain) (string, bool) {
if !chain.HasElse {
return "", false
}
@@ -41,7 +63,7 @@ func (*SuperfluousElseRule) checkIfElse(chain ifelse.Chain, args ifelse.Args) (s
return "", false
}
if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.Else.HasDecls()) {
if e.preserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.Else.HasDecls()) {
// avoid increasing variable scope
return "", false
}

View File

@@ -0,0 +1,80 @@
package rule
import (
"testing"
"github.com/mgechev/revive/lint"
)
func TestSuperfluousElseRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantPreserveScope bool
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantPreserveScope: false,
},
{
name: "valid arguments",
arguments: lint.Arguments{
"preserveScope",
},
wantErr: nil,
wantPreserveScope: true,
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
"preservescope",
},
wantErr: nil,
wantPreserveScope: true,
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
"preserve-scope",
},
wantErr: nil,
wantPreserveScope: true,
},
{
name: "invalid arguments",
arguments: lint.Arguments{
"unknown",
},
wantErr: nil,
wantPreserveScope: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule SuperfluousElseRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if rule.preserveScope != tt.wantPreserveScope {
t.Errorf("unexpected preserveScope: got = %v, want = %v", rule.preserveScope, tt.wantPreserveScope)
}
})
}
}

View File

@@ -32,15 +32,13 @@ func (r *UncheckedTypeAssertionRule) Configure(arguments lint.Arguments) error {
}
for k, v := range args {
switch k {
case "acceptIgnoredAssertionResult":
if !isRuleOption(k, "acceptIgnoredAssertionResult") {
return fmt.Errorf("unknown argument: %s", k)
}
r.acceptIgnoredAssertionResult, ok = v.(bool)
if !ok {
return fmt.Errorf("unable to parse argument '%s'. Expected boolean", k)
}
default:
return fmt.Errorf("unknown argument: %s", k)
}
}
return nil
}

View File

@@ -0,0 +1,92 @@
package rule
import (
"errors"
"testing"
"github.com/mgechev/revive/lint"
)
func TestUncheckedTypeAssertionRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantAcceptIgnoredAssertionResult bool
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantAcceptIgnoredAssertionResult: false,
},
{
name: "valid acceptIgnoredAssertionResult argument",
arguments: lint.Arguments{map[string]any{
"acceptIgnoredAssertionResult": true,
}},
wantErr: nil,
wantAcceptIgnoredAssertionResult: true,
},
{
name: "valid lowercased argument",
arguments: lint.Arguments{map[string]any{
"acceptignoredassertionresult": true,
}},
wantErr: nil,
wantAcceptIgnoredAssertionResult: true,
},
{
name: "valid kebab-cased argument",
arguments: lint.Arguments{map[string]any{
"accept-ignored-assertion-result": true,
}},
wantErr: nil,
wantAcceptIgnoredAssertionResult: true,
},
{
name: "invalid type",
arguments: lint.Arguments{123},
wantErr: errors.New("unable to get arguments. Expected object of key-value-pairs"),
},
{
name: "invalid acceptIgnoredAssertionResult type",
arguments: lint.Arguments{map[string]any{
"acceptIgnoredAssertionResult": "true",
}},
wantErr: errors.New("unable to parse argument 'acceptIgnoredAssertionResult'. Expected boolean"),
},
{
name: "unknown argument",
arguments: lint.Arguments{map[string]any{
"unknownKey": true,
}},
wantErr: errors.New("unknown argument: unknownKey"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule UncheckedTypeAssertionRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if rule.acceptIgnoredAssertionResult != tt.wantAcceptIgnoredAssertionResult {
t.Errorf("unexpected acceptIgnoredAssertionResult: got = %v, want %v", rule.acceptIgnoredAssertionResult, tt.wantAcceptIgnoredAssertionResult)
}
})
}
}

View File

@@ -28,17 +28,20 @@ func (r *UnusedParamRule) Configure(args lint.Arguments) error {
if len(args) == 0 {
return nil
}
// Arguments = [{}]
options := args[0].(map[string]any)
allowRegexParam, ok := options["allowRegex"]
options, ok := args[0].(map[string]any)
if !ok {
// Arguments = [{}]
return nil
}
// Arguments = [{allowRegex="^_"}]
allowRegexStr, ok := allowRegexParam.(string)
for k, v := range options {
if !isRuleOption(k, "allowRegex") {
return nil
}
// Arguments = [{allowRegex="_"}]
allowRegexStr, ok := v.(string)
if !ok {
return fmt.Errorf("error configuring %s rule: allowRegex is not string but [%T]", r.Name(), allowRegexParam)
return fmt.Errorf("error configuring %s rule: allowRegex is not string but [%T]", r.Name(), v)
}
var err error
r.allowRegex, err = regexp.Compile(allowRegexStr)
@@ -46,6 +49,7 @@ func (r *UnusedParamRule) Configure(args lint.Arguments) error {
return fmt.Errorf("error configuring %s rule: allowRegex is not valid regex [%s]: %w", r.Name(), allowRegexStr, err)
}
r.failureMsg = "parameter '%s' seems to be unused, consider removing or renaming it to match " + r.allowRegex.String()
}
return nil
}

118
rule/unused_param_test.go Normal file
View File

@@ -0,0 +1,118 @@
package rule
import (
"errors"
"reflect"
"regexp"
"testing"
"github.com/mgechev/revive/lint"
)
func TestUnusedParamRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantAllowRegex *regexp.Regexp
wantFailureMsg string
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantAllowRegex: regexp.MustCompile("^_$"),
wantFailureMsg: "parameter '%s' seems to be unused, consider removing or renaming it as _",
},
{
name: "valid arguments",
arguments: lint.Arguments{
map[string]any{
"allowRegex": "^_",
},
},
wantErr: nil,
wantAllowRegex: regexp.MustCompile("^_"),
wantFailureMsg: "parameter '%s' seems to be unused, consider removing or renaming it to match ^_",
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
map[string]any{
"allowregex": "^_",
},
},
wantErr: nil,
wantAllowRegex: regexp.MustCompile("^_"),
wantFailureMsg: "parameter '%s' seems to be unused, consider removing or renaming it to match ^_",
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
map[string]any{
"allow-regex": "^_",
},
},
wantErr: nil,
wantAllowRegex: regexp.MustCompile("^_"),
wantFailureMsg: "parameter '%s' seems to be unused, consider removing or renaming it to match ^_",
},
{
name: "missed allowRegex value",
arguments: lint.Arguments{
map[string]any{
"unknownKey": "123",
},
},
wantErr: nil,
wantAllowRegex: regexp.MustCompile("^_$"),
wantFailureMsg: "parameter '%s' seems to be unused, consider removing or renaming it as _",
},
{
name: "invalid allowRegex: not a string",
arguments: lint.Arguments{
map[string]any{
"allowRegex": 123,
},
},
wantErr: errors.New("error configuring unused-parameter rule: allowRegex is not string but [int]"),
},
{
name: "invalid allowRegex: not a valid regex",
arguments: lint.Arguments{
map[string]any{
"allowRegex": "[",
},
},
wantErr: errors.New("error configuring unused-parameter rule: allowRegex is not valid regex [[]: error parsing regexp: missing closing ]: " + "`[`"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule UnusedParamRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if !reflect.DeepEqual(rule.allowRegex, tt.wantAllowRegex) {
t.Errorf("unexpected allowRegex: got = %v, want %v", rule.allowRegex, tt.wantAllowRegex)
}
if rule.failureMsg != tt.wantFailureMsg {
t.Errorf("unexpected failureMessage: got = %v, want %v", rule.failureMsg, tt.wantFailureMsg)
}
})
}
}

View File

@@ -27,16 +27,19 @@ func (r *UnusedReceiverRule) Configure(args lint.Arguments) error {
return nil
}
// Arguments = [{}]
options := args[0].(map[string]any)
allowRegexParam, ok := options["allowRegex"]
options, ok := args[0].(map[string]any)
if !ok {
return nil
}
for k, v := range options {
if !isRuleOption(k, "allowRegex") {
return nil
}
// Arguments = [{allowRegex="^_"}]
allowRegexStr, ok := allowRegexParam.(string)
allowRegexStr, ok := v.(string)
if !ok {
return fmt.Errorf("error configuring [unused-receiver] rule: allowRegex is not string but [%T]", allowRegexParam)
return fmt.Errorf("error configuring [unused-receiver] rule: allowRegex is not string but [%T]", v)
}
var err error
r.allowRegex, err = regexp.Compile(allowRegexStr)
@@ -44,6 +47,7 @@ func (r *UnusedReceiverRule) Configure(args lint.Arguments) error {
return fmt.Errorf("error configuring [unused-receiver] rule: allowRegex is not valid regex [%s]: %w", allowRegexStr, err)
}
r.failureMsg = "method receiver '%s' is not referenced in method's body, consider removing or renaming it to match " + r.allowRegex.String()
}
return nil
}

View File

@@ -0,0 +1,123 @@
package rule
import (
"errors"
"regexp"
"testing"
"github.com/mgechev/revive/lint"
)
func TestUnusedReceiverRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantRegex *regexp.Regexp
wantFailureMsg string
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantRegex: regexp.MustCompile("^_$"),
wantFailureMsg: "method receiver '%s' is not referenced in method's body, consider removing or renaming it as _",
},
{
name: "valid arguments",
arguments: lint.Arguments{
map[string]any{
"allowRegex": "^_",
},
},
wantErr: nil,
wantRegex: regexp.MustCompile("^_"),
wantFailureMsg: "method receiver '%s' is not referenced in method's body, consider removing or renaming it to match ^_",
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
map[string]any{
"allowregex": "^_",
},
},
wantErr: nil,
wantRegex: regexp.MustCompile("^_"),
wantFailureMsg: "method receiver '%s' is not referenced in method's body, consider removing or renaming it to match ^_",
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
map[string]any{
"allow-regex": "^_",
},
},
wantErr: nil,
wantRegex: regexp.MustCompile("^_"),
wantFailureMsg: "method receiver '%s' is not referenced in method's body, consider removing or renaming it to match ^_",
},
{
name: "argument is not a map",
arguments: lint.Arguments{
"invalid_argument",
},
wantErr: nil,
wantFailureMsg: "method receiver '%s' is not referenced in method's body, consider removing or renaming it as _",
},
{
name: "missing allowRegex key",
arguments: lint.Arguments{
map[string]any{},
},
wantErr: nil,
wantRegex: allowBlankIdentifierRegex,
wantFailureMsg: "method receiver '%s' is not referenced in method's body, consider removing or renaming it as _",
},
{
name: "invalid allowRegex type",
arguments: lint.Arguments{
map[string]any{
"allowRegex": 123,
},
},
wantErr: errors.New("error configuring [unused-receiver] rule: allowRegex is not string but [int]"),
},
{
name: "invalid allowRegex value",
arguments: lint.Arguments{
map[string]any{
"allowRegex": "[invalid",
},
},
wantErr: errors.New("error configuring [unused-receiver] rule: allowRegex is not valid regex [[invalid]: error parsing regexp: missing closing ]: `[invalid`"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule UnusedReceiverRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if tt.wantRegex != nil && rule.allowRegex.String() != tt.wantRegex.String() {
t.Errorf("unexpected allowRegex: got = %v, want = %v", rule.allowRegex.String(), tt.wantRegex.String())
}
if tt.wantFailureMsg != rule.failureMsg {
t.Errorf("unexpected failureMsg: got = %v, want = %v", rule.failureMsg, tt.wantFailureMsg)
}
})
}
}

View File

@@ -7,6 +7,7 @@ import (
"go/printer"
"go/token"
"regexp"
"strings"
"github.com/mgechev/revive/lint"
)
@@ -112,6 +113,18 @@ func checkNumberOfArguments(expected int, args lint.Arguments, ruleName string)
return nil
}
// isRuleOption returns true if arg and name are the same after normalization.
func isRuleOption(arg, name string) bool {
return normalizeRuleOption(arg) == normalizeRuleOption(name)
}
// normalizeRuleOption returns an option name from the argument. It is lowercased and without hyphens.
//
// Example: normalizeRuleOption("allowTypesBefore"), normalizeRuleOption("allow-types-before") -> "allowtypesbefore"
func normalizeRuleOption(arg string) string {
return strings.ToLower(strings.ReplaceAll(arg, "-", ""))
}
var directiveCommentRE = regexp.MustCompile("^//(line |extern |export |[a-z0-9]+:[a-z0-9])") // see https://go-review.googlesource.com/c/website/+/442516/1..2/_content/doc/comment.md#494
func isDirectiveComment(line string) bool {

View File

@@ -64,8 +64,14 @@ func (r *VarNamingRule) Configure(arguments lint.Arguments) error {
if !ok {
return fmt.Errorf("invalid third argument to the var-naming rule. Expecting a %s of type slice, of len==1, with map, but %T", "options", asSlice[0])
}
r.allowUpperCaseConst = fmt.Sprint(args["upperCaseConst"]) == "true"
r.skipPackageNameChecks = fmt.Sprint(args["skipPackageNameChecks"]) == "true"
for k, v := range args {
switch {
case isRuleOption(k, "upperCaseConst"):
r.allowUpperCaseConst = fmt.Sprint(v) == "true"
case isRuleOption(k, "skipPackageNameChecks"):
r.skipPackageNameChecks = fmt.Sprint(v) == "true"
}
}
}
return nil
}
@@ -279,7 +285,7 @@ func getList(arg any, argName string) ([]string, error) {
for _, v := range args {
val, ok := v.(string)
if !ok {
return nil, fmt.Errorf("invalid %s values of the var-naming rule. Expecting slice of strings but got element of type %T", val, arg)
return nil, fmt.Errorf("invalid %v values of the var-naming rule. Expecting slice of strings but got element of type %T", v, arg)
}
list = append(list, val)
}

136
rule/var_naming_test.go Normal file
View File

@@ -0,0 +1,136 @@
package rule
import (
"errors"
"testing"
"github.com/mgechev/revive/lint"
)
func TestVarNamingRule_Configure(t *testing.T) {
tests := []struct {
name string
arguments lint.Arguments
wantErr error
wantAllowList []string
wantBlockList []string
wantAllowUpperCaseConst bool
wantSkipPackageNameChecks bool
}{
{
name: "no arguments",
arguments: lint.Arguments{},
wantErr: nil,
wantAllowList: nil,
wantBlockList: nil,
wantAllowUpperCaseConst: false,
wantSkipPackageNameChecks: false,
},
{
name: "valid arguments",
arguments: lint.Arguments{
[]any{"ID"},
[]any{"VM"},
[]any{map[string]any{
"upperCaseConst": true,
"skipPackageNameChecks": true,
}},
},
wantErr: nil,
wantAllowList: []string{"ID"},
wantBlockList: []string{"VM"},
wantAllowUpperCaseConst: true,
wantSkipPackageNameChecks: true,
},
{
name: "valid lowercased arguments",
arguments: lint.Arguments{
[]any{"ID"},
[]any{"VM"},
[]any{map[string]any{
"uppercaseconst": true,
"skippackagenamechecks": true,
}},
},
wantErr: nil,
wantAllowList: []string{"ID"},
wantBlockList: []string{"VM"},
wantAllowUpperCaseConst: true,
wantSkipPackageNameChecks: true,
},
{
name: "valid kebab-cased arguments",
arguments: lint.Arguments{
[]any{"ID"},
[]any{"VM"},
[]any{map[string]any{
"upper-case-const": true,
"skip-package-name-checks": true,
}},
},
wantErr: nil,
wantAllowList: []string{"ID"},
wantBlockList: []string{"VM"},
wantAllowUpperCaseConst: true,
wantSkipPackageNameChecks: true,
},
{
name: "invalid allowlist type",
arguments: lint.Arguments{123},
wantErr: errors.New("invalid argument to the var-naming rule. Expecting a allowlist of type slice with initialisms, got int"),
},
{
name: "invalid allowlist value type",
arguments: lint.Arguments{[]any{123}},
wantErr: errors.New("invalid 123 values of the var-naming rule. Expecting slice of strings but got element of type []interface {}"),
},
{
name: "invalid blocklist type",
arguments: lint.Arguments{[]any{"ID"}, 123},
wantErr: errors.New("invalid argument to the var-naming rule. Expecting a blocklist of type slice with initialisms, got int"),
},
{
name: "invalid third argument type",
arguments: lint.Arguments{[]any{"ID"}, []any{"VM"}, 123},
wantErr: errors.New("invalid third argument to the var-naming rule. Expecting a options of type slice, got int"),
},
{
name: "invalid third argument slice size",
arguments: lint.Arguments{[]any{"ID"}, []any{"VM"}, []any{}},
wantErr: errors.New("invalid third argument to the var-naming rule. Expecting a options of type slice, of len==1, but 0"),
},
{
name: "invalid third argument first element type",
arguments: lint.Arguments{[]any{"ID"}, []any{"VM"}, []any{123}},
wantErr: errors.New("invalid third argument to the var-naming rule. Expecting a options of type slice, of len==1, with map, but int"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rule VarNamingRule
err := rule.Configure(tt.arguments)
if tt.wantErr != nil {
if err == nil {
t.Errorf("unexpected error: got = nil, want = %v", tt.wantErr)
return
}
if err.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error: got = %v, want = %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Errorf("unexpected error: got = %v, want = nil", err)
}
if rule.allowUpperCaseConst != tt.wantAllowUpperCaseConst {
t.Errorf("unexpected allowUpperCaseConst: got = %v, want %v", rule.allowUpperCaseConst, tt.wantAllowUpperCaseConst)
}
if rule.skipPackageNameChecks != tt.wantSkipPackageNameChecks {
t.Errorf("unexpected skipPackageNameChecks: got = %v, want %v", rule.skipPackageNameChecks, tt.wantSkipPackageNameChecks)
}
})
}
}

View File

@@ -21,4 +21,13 @@ func TestAddConstantWithArguments(t *testing.T) {
"ignoreFuncs": "os\\.(CreateFile|WriteFile|Chmod|FindProcess),\\.Println,ignoredFunc,\\.Info",
}},
})
testRule(t, "add_constant", &rule.AddConstantRule{}, &lint.RuleConfig{
Arguments: []any{map[string]any{
"max-lit-count": "2",
"allow-strs": `""`,
"allow-ints": "0,1,2",
"allow-floats": "0.0,1.0",
"ignore-funcs": `os\.(CreateFile|WriteFile|Chmod|FindProcess),\.Println,ignoredFunc,\.Info`,
}},
})
}

View File

@@ -18,6 +18,12 @@ func TestContextAsArgument(t *testing.T) {
"allowTypesBefore": "AllowedBeforeType,AllowedBeforeStruct,*AllowedBeforePtrStruct,*testing.T",
},
},
})
testRule(t, "context_as_argument", &rule.ContextAsArgumentRule{}, &lint.RuleConfig{
Arguments: []any{
map[string]any{
"allow-types-before": "AllowedBeforeType,AllowedBeforeStruct,*AllowedBeforePtrStruct,*testing.T",
},
)
},
})
}

View File

@@ -12,6 +12,9 @@ func TestDefer(t *testing.T) {
}
func TestDeferLoopDisabled(t *testing.T) {
testRule(t, "defer_loop_disabled", &rule.DeferRule{}, &lint.RuleConfig{
Arguments: []any{[]any{"return", "recover", "callChain", "methodCall"}},
})
testRule(t, "defer_loop_disabled", &rule.DeferRule{}, &lint.RuleConfig{
Arguments: []any{[]any{"return", "recover", "call-chain", "method-call"}},
})

View File

@@ -17,4 +17,9 @@ func TestDotImports(t *testing.T) {
"allowedPackages": []any{"errors", "context", "github.com/BurntSushi/toml"},
}},
})
testRule(t, "dot_imports", &rule.DotImportsRule{}, &lint.RuleConfig{
Arguments: []any{map[string]any{
"allowed-packages": []any{"errors", "context", "github.com/BurntSushi/toml"},
}},
})
}

View File

@@ -3,13 +3,14 @@ package test
import (
"testing"
"github.com/mgechev/revive/internal/ifelse"
"github.com/mgechev/revive/lint"
"github.com/mgechev/revive/rule"
)
func TestEarlyReturn(t *testing.T) {
testRule(t, "early_return", &rule.EarlyReturnRule{})
testRule(t, "early_return_scope", &rule.EarlyReturnRule{}, &lint.RuleConfig{Arguments: []any{ifelse.PreserveScope}})
testRule(t, "early_return_jump", &rule.EarlyReturnRule{}, &lint.RuleConfig{Arguments: []any{ifelse.AllowJump}})
testRule(t, "early_return_scope", &rule.EarlyReturnRule{}, &lint.RuleConfig{Arguments: []any{"preserveScope"}})
testRule(t, "early_return_scope", &rule.EarlyReturnRule{}, &lint.RuleConfig{Arguments: []any{"preserve-scope"}})
testRule(t, "early_return_jump", &rule.EarlyReturnRule{}, &lint.RuleConfig{Arguments: []any{"allowJump"}})
testRule(t, "early_return_jump", &rule.EarlyReturnRule{}, &lint.RuleConfig{Arguments: []any{"allow-jump"}})
}

View File

@@ -26,6 +26,13 @@ func TestEnforceRepeatedArgTypeStyleShort(t *testing.T) {
},
},
})
testRule(t, "enforce_repeated_arg_type_style_short_args", &rule.EnforceRepeatedArgTypeStyleRule{}, &lint.RuleConfig{
Arguments: []any{
map[string]any{
"func-arg-style": `short`,
},
},
})
testRule(t, "enforce_repeated_arg_type_style_short_return", &rule.EnforceRepeatedArgTypeStyleRule{}, &lint.RuleConfig{
Arguments: []any{
map[string]any{
@@ -33,6 +40,13 @@ func TestEnforceRepeatedArgTypeStyleShort(t *testing.T) {
},
},
})
testRule(t, "enforce_repeated_arg_type_style_short_return", &rule.EnforceRepeatedArgTypeStyleRule{}, &lint.RuleConfig{
Arguments: []any{
map[string]any{
"func-ret-val-style": `short`,
},
},
})
}
func TestEnforceRepeatedArgTypeStyleFull(t *testing.T) {

View File

@@ -32,9 +32,12 @@ func TestCheckPublicInterfaceOption(t *testing.T) {
}
func TestCheckDisablingOnDeclarationTypes(t *testing.T) {
args := []any{"disableChecksOnConstants", "disableChecksOnFunctions", "disableChecksOnMethods", "disableChecksOnTypes", "disableChecksOnVariables"}
testRule(t, "exported_issue_1045", &rule.ExportedRule{}, &lint.RuleConfig{Arguments: args})
testRule(t, "exported_issue_1045", &rule.ExportedRule{}, &lint.RuleConfig{
Arguments: []any{"disableChecksOnConstants", "disableChecksOnFunctions", "disableChecksOnMethods", "disableChecksOnTypes", "disableChecksOnVariables"},
})
testRule(t, "exported_issue_1045", &rule.ExportedRule{}, &lint.RuleConfig{
Arguments: []any{"disable-checks-on-constants", "disable-checks-on-functions", "disable-checks-on-methods", "disable-checks-on-types", "disable-checks-on-variables"},
})
}
func TestCheckDirectiveComment(t *testing.T) {

View File

@@ -29,4 +29,7 @@ func TestFileLengthLimit(t *testing.T) {
testRule(t, "file_length_limit_4_skip_comments_skip_blank", &rule.FileLengthLimitRule{}, &lint.RuleConfig{
Arguments: []any{map[string]any{"max": int64(4), "skipComments": true, "skipBlankLines": true}},
})
testRule(t, "file_length_limit_4_skip_comments_skip_blank", &rule.FileLengthLimitRule{}, &lint.RuleConfig{
Arguments: []any{map[string]any{"max": int64(4), "skip-comments": true, "skip-blank-lines": true}},
})
}

View File

@@ -31,6 +31,14 @@ func TestImportAliasNaming_CustomConfigWithMultipleRules(t *testing.T) {
},
},
})
testRule(t, "import_alias_naming_custom_config_with_multiple_values", &rule.ImportAliasNamingRule{}, &lint.RuleConfig{
Arguments: []any{
map[string]any{
"allow-regex": `^[a-z][a-z0-9]*$`,
"deny-regex": `^((v\d+)|(v\d+alpha\d+))$`,
},
},
})
}
func TestImportAliasNaming_CustomConfigWithOnlyDeny(t *testing.T) {

View File

@@ -0,0 +1,14 @@
package test
import (
"testing"
"github.com/mgechev/revive/lint"
"github.com/mgechev/revive/rule"
)
func TestIndentErrorFlow(t *testing.T) {
testRule(t, "indent_error_flow", &rule.IndentErrorFlowRule{})
testRule(t, "indent_error_flow_scope", &rule.IndentErrorFlowRule{}, &lint.RuleConfig{Arguments: []any{"preserveScope"}})
testRule(t, "indent_error_flow_scope", &rule.IndentErrorFlowRule{}, &lint.RuleConfig{Arguments: []any{"preserve-scope"}})
}

View File

@@ -16,9 +16,18 @@ func TestReceiverNamingTypeParams(t *testing.T) {
}
func TestReceiverNamingMaxLength(t *testing.T) {
args := []any{map[string]any{
"maxLength": int64(2),
}}
testRule(t, "receiver_naming_issue_1040", &rule.ReceiverNamingRule{},
&lint.RuleConfig{Arguments: args})
&lint.RuleConfig{
Arguments: []any{
map[string]any{"maxLength": int64(2)},
},
},
)
testRule(t, "receiver_naming_issue_1040", &rule.ReceiverNamingRule{},
&lint.RuleConfig{
Arguments: []any{
map[string]any{"max-length": int64(2)},
},
},
)
}

View File

@@ -3,12 +3,12 @@ package test
import (
"testing"
"github.com/mgechev/revive/internal/ifelse"
"github.com/mgechev/revive/lint"
"github.com/mgechev/revive/rule"
)
func TestSuperfluousElse(t *testing.T) {
testRule(t, "superfluous_else", &rule.SuperfluousElseRule{})
testRule(t, "superfluous_else_scope", &rule.SuperfluousElseRule{}, &lint.RuleConfig{Arguments: []any{ifelse.PreserveScope}})
testRule(t, "superfluous_else_scope", &rule.SuperfluousElseRule{}, &lint.RuleConfig{Arguments: []any{"preserveScope"}})
testRule(t, "superfluous_else_scope", &rule.SuperfluousElseRule{}, &lint.RuleConfig{Arguments: []any{"preserve-scope"}})
}

View File

@@ -12,9 +12,18 @@ func TestUncheckedDynamicCast(t *testing.T) {
}
func TestUncheckedDynamicCastWithAcceptIgnored(t *testing.T) {
args := []any{map[string]any{
"acceptIgnoredAssertionResult": true,
}}
testRule(t, "unchecked_type_assertion_accept_ignored", &rule.UncheckedTypeAssertionRule{}, &lint.RuleConfig{Arguments: args})
testRule(t, "unchecked_type_assertion_accept_ignored", &rule.UncheckedTypeAssertionRule{},
&lint.RuleConfig{
Arguments: []any{
map[string]any{"acceptIgnoredAssertionResult": true},
},
},
)
testRule(t, "unchecked_type_assertion_accept_ignored", &rule.UncheckedTypeAssertionRule{},
&lint.RuleConfig{
Arguments: []any{
map[string]any{"accept-ignored-assertion-result": true},
},
},
)
}

View File

@@ -16,6 +16,9 @@ func TestUnusedParam(t *testing.T) {
testRule(t, "unused_param_custom_regex", &rule.UnusedParamRule{}, &lint.RuleConfig{Arguments: []any{
map[string]any{"allowRegex": "^xxx"},
}})
testRule(t, "unused_param_custom_regex", &rule.UnusedParamRule{}, &lint.RuleConfig{Arguments: []any{
map[string]any{"allow-regex": "^xxx"},
}})
}
func BenchmarkUnusedParam(b *testing.B) {

View File

@@ -16,6 +16,9 @@ func TestUnusedReceiver(t *testing.T) {
testRule(t, "unused_receiver_custom_regex", &rule.UnusedReceiverRule{}, &lint.RuleConfig{Arguments: []any{
map[string]any{"allowRegex": "^xxx"},
}})
testRule(t, "unused_receiver_custom_regex", &rule.UnusedReceiverRule{}, &lint.RuleConfig{Arguments: []any{
map[string]any{"allow-regex": "^xxx"},
}})
}
func BenchmarkUnusedReceiver(b *testing.B) {

View File

@@ -18,9 +18,15 @@ func TestVarNaming(t *testing.T) {
testRule(t, "var_naming_upper_case_const_true", &rule.VarNamingRule{}, &lint.RuleConfig{
Arguments: []any{[]any{}, []any{}, []any{map[string]any{"upperCaseConst": true}}},
})
testRule(t, "var_naming_upper_case_const_true", &rule.VarNamingRule{}, &lint.RuleConfig{
Arguments: []any{[]any{}, []any{}, []any{map[string]any{"upper-case-const": true}}},
})
testRule(t, "var_naming_skip_package_name_checks_false", &rule.VarNamingRule{}, &lint.RuleConfig{})
testRule(t, "var_naming_skip_package_name_checks_true", &rule.VarNamingRule{}, &lint.RuleConfig{
Arguments: []any{[]any{}, []any{}, []any{map[string]any{"skipPackageNameChecks": true}}},
})
testRule(t, "var_naming_skip_package_name_checks_true", &rule.VarNamingRule{}, &lint.RuleConfig{
Arguments: []any{[]any{}, []any{}, []any{map[string]any{"skip-package-name-checks": true}}},
})
}

49
testdata/indent_error_flow.go vendored Normal file
View File

@@ -0,0 +1,49 @@
// Test of return+else warning.
// Package pkg ...
package pkg
import "log"
func f(x int) bool {
if x > 0 {
return true
} else { // MATCH /if block ends with a return statement, so drop this else and outdent its block/
log.Printf("non-positive x: %d", x)
}
return false
}
func g(f func() bool) string {
if ok := f(); ok {
return "it's okay"
} else { // MATCH /if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)/
return "it's NOT okay!"
}
}
func h(f func() bool, x int) string {
if err == author.ErrCourseNotFound {
return
} else if err == author.ErrCourseAccess {
// side effect
} else if err == author.AnotherError {
return "okay"
} else {
if ok := f(); ok {
return "it's okay"
} else { // MATCH /if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)/
return "it's NOT okay!"
}
}
}
func i() string {
if err == author.ErrCourseNotFound {
return "not found"
} else if err == author.AnotherError {
return "something else"
} else { // MATCH /if block ends with a return statement, so drop this else and outdent its block/
return "okay"
}
}

15
testdata/indent_error_flow_scope.go vendored Normal file
View File

@@ -0,0 +1,15 @@
package pkg
func fn4() {
if cond {
var x = fn2()
fn3(x)
return
} else {
y := fn2()
fn3(y)
}
// Don't want to move the declaration of x here since it stays in scope afterward
y := fn2()
fn3(y)
}