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:
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
193
rule/add_constant_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
117
rule/context_as_argument_test.go
Normal file
117
rule/context_as_argument_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
137
rule/defer_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
127
rule/dot_imports_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
92
rule/early_return_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
158
rule/enforce_repeated_arg_type_style_test.go
Normal file
158
rule/enforce_repeated_arg_type_style_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
178
rule/exported_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
121
rule/file_length_limit_test.go
Normal file
121
rule/file_length_limit_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
133
rule/import_alias_naming_test.go
Normal file
133
rule/import_alias_naming_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
80
rule/indent_error_flow_test.go
Normal file
80
rule/indent_error_flow_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
92
rule/receiver_naming_test.go
Normal file
92
rule/receiver_naming_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
80
rule/superfluous_else_test.go
Normal file
80
rule/superfluous_else_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
92
rule/unchecked_type_assertion_test.go
Normal file
92
rule/unchecked_type_assertion_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
118
rule/unused_param_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
123
rule/unused_receiver_test.go
Normal file
123
rule/unused_receiver_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
136
rule/var_naming_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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`,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"}},
|
||||
})
|
||||
|
||||
@@ -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"},
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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"}})
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
14
test/indent_error_flow_test.go
Normal file
14
test/indent_error_flow_test.go
Normal 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"}})
|
||||
}
|
||||
@@ -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)},
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"}})
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
49
testdata/indent_error_flow.go
vendored
Normal 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
15
testdata/indent_error_flow_scope.go
vendored
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user