1
0
mirror of https://github.com/mgechev/revive.git synced 2024-11-21 17:16:40 +02:00

Conf reason rule disabling (#193)

* adds support for comments when enabling/disabling

* adds config flag to require disabling reason

* Update lint/file.go

adds code fmt suggestion by @mgechev

Co-Authored-By: Minko Gechev <mgechev@gmail.com>

* moves regexp compilation out of the function
fix typo in condition

* adds support for comments when enabling/disabling

* skips incomplete directives and generate a failure

* adds _directive_ concept to cope with specify-disable-reason

* adds doc
gofmt

* fixes severity is ignored
This commit is contained in:
SalvadorC 2019-08-02 17:21:33 +02:00 committed by Minko Gechev
parent 639585fb79
commit 55cfae63e9
16 changed files with 105 additions and 28 deletions

View File

@ -140,7 +140,7 @@ revive -config revive.toml -exclude file1.go -exclude file2.go -formatter friend
- The output will be formatted with the `friendly` formatter
- The linter will analyze `github.com/mgechev/revive` and the files in `package`
### Comment Annotations
### Comment Directives
Using comments, you can disable the linter for the entire file or only range of lines:
@ -167,6 +167,28 @@ func Public() private {
This way, `revive` will not warn you for that you're returning an object of an unexported type, from an exported function.
You can document why you disable the linter by adding a trailing text in the directive, for example
```go
//revive:disable Until the code is stable
```
```go
//revive:disable:cyclomatic High complexity score but easy to understand
```
You can also configure `revive` to enforce documenting linter disabling directives by adding
```toml
[directive.specify-disable-reason]
```
in the configuration. You can set the severity (defaults to _warning_) of the violation of this directive
```toml
[directive.specify-disable-reason]
severity = "error"
```
### Configuration
`revive` can be configured with a TOML file. Here's a sample configuration with explanation for the individual properties:
@ -399,7 +421,7 @@ Each formatter needs to implement the following interface:
```go
type Formatter interface {
Format(<-chan Failure, RulesConfig) (string, error)
Format(<-chan Failure, Config) (string, error)
Name() string
}
```

View File

@ -142,6 +142,12 @@ func normalizeConfig(config *lint.Config) {
}
config.Rules[k] = v
}
for k, v := range config.Directives {
if v.Severity == "" {
v.Severity = severity
}
config.Directives[k] = v
}
}
}

View File

@ -6,6 +6,13 @@ func foo1() {
var invalid_name2 = 1 //revive:disable-line:var-naming
}
func foo11() {
//revive:disable-next-line:var-naming I'm an Eiffel programmer thus I like underscores
var invalid_name = 0
var invalid_name2 = 1 //revive:disable-line:var-naming I'm an Eiffel programmer thus I like underscores
}
func foo2() {
// revive:disable-next-line:var-naming
//revive:disable
@ -22,3 +29,10 @@ func foo3() {
/* revive:disable-next-line:var-naming */
var invalid_name3 = 0 // MATCH /don't use underscores in Go names; var invalid_name3 should be invalidName3/
}
func foo2p1() {
//revive:disable Underscores are fine
var invalid_name = 0
//revive:enable No! Underscores are not nice!
var invalid_name2 = 1 // MATCH /don't use underscores in Go names; var invalid_name2 should be invalidName2/
}

View File

@ -28,7 +28,7 @@ type issue struct {
}
// Format formats the failures gotten from the lint.
func (f *Checkstyle) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
func (f *Checkstyle) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
var issues = map[string][]issue{}
for failure := range failures {
buf := new(bytes.Buffer)

View File

@ -18,7 +18,7 @@ func (f *Default) Name() string {
}
// Format formats the failures gotten from the lint.
func (f *Default) Format(failures <-chan lint.Failure, _ lint.RulesConfig) (string, error) {
func (f *Default) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) {
for failure := range failures {
fmt.Printf("%v: %s\n", failure.Position.Start, failure.Failure)
}

View File

@ -37,7 +37,7 @@ func (f *Friendly) Name() string {
}
// Format formats the failures gotten from the lint.
func (f *Friendly) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
errorMap := map[string]int{}
warningMap := map[string]int{}
totalErrors := 0

View File

@ -24,7 +24,7 @@ type jsonObject struct {
}
// Format formats the failures gotten from the lint.
func (f *JSON) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
func (f *JSON) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
var slice []jsonObject
for failure := range failures {
obj := jsonObject{}

View File

@ -19,7 +19,7 @@ func (f *NDJSON) Name() string {
}
// Format formats the failures gotten from the lint.
func (f *NDJSON) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
func (f *NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
enc := json.NewEncoder(os.Stdout)
for failure := range failures {
obj := jsonObject{}

View File

@ -18,7 +18,7 @@ func (f *Plain) Name() string {
}
// Format formats the failures gotten from the lint.
func (f *Plain) Format(failures <-chan lint.Failure, _ lint.RulesConfig) (string, error) {
func (f *Plain) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) {
for failure := range failures {
fmt.Printf("%v: %s %s\n", failure.Position.Start, failure.Failure, "https://revive.run/r#"+failure.RuleName)
}

View File

@ -2,8 +2,11 @@ package formatter
import "github.com/mgechev/revive/lint"
func severity(config lint.RulesConfig, failure lint.Failure) lint.Severity {
if config, ok := config[failure.RuleName]; ok && config.Severity == lint.SeverityError {
func severity(config lint.Config, failure lint.Failure) lint.Severity {
if config, ok := config.Rules[failure.RuleName]; ok && config.Severity == lint.SeverityError {
return lint.SeverityError
}
if config, ok := config.Directives[failure.RuleName]; ok && config.Severity == lint.SeverityError {
return lint.SeverityError
}
return lint.SeverityWarning

View File

@ -32,7 +32,7 @@ func formatFailure(failure lint.Failure, severity lint.Severity) []string {
}
// Format formats the failures gotten from the lint.
func (f *Stylish) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) {
func (f *Stylish) Format(failures <-chan lint.Failure, config lint.Config) (string, error) {
var result [][]string
var totalErrors = 0
var total = 0

View File

@ -19,7 +19,7 @@ func (f *Unix) Name() string {
}
// Format formats the failures gotten from the lint.
func (f *Unix) Format(failures <-chan lint.Failure, _ lint.RulesConfig) (string, error) {
func (f *Unix) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) {
for failure := range failures {
fmt.Printf("%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure)
}

View File

@ -12,12 +12,21 @@ type RuleConfig struct {
// RulesConfig defines the config for all rules.
type RulesConfig = map[string]RuleConfig
// DirectiveConfig is type used for the linter directive configuration.
type DirectiveConfig struct {
Severity Severity
}
// DirectivesConfig defines the config for all directives.
type DirectivesConfig = map[string]DirectiveConfig
// Config defines the config of the linter.
type Config struct {
IgnoreGeneratedHeader bool `toml:"ignoreGeneratedHeader"`
Confidence float64
Severity Severity
Rules RulesConfig `toml:"rule"`
ErrorCode int `toml:"errorCode"`
WarningCode int `toml:"warningCode"`
Rules RulesConfig `toml:"rule"`
ErrorCode int `toml:"errorCode"`
WarningCode int `toml:"warningCode"`
Directives DirectivesConfig `toml:"directive"`
}

View File

@ -97,9 +97,12 @@ func (f *File) isMain() bool {
return false
}
const directiveSpecifyDisableReason = "specify-disable-reason"
func (f *File) lint(rules []Rule, config Config, failures chan Failure) {
rulesConfig := config.Rules
disabledIntervals := f.disabledIntervals(rules)
_, mustSpecifyDisableReason := config.Directives[directiveSpecifyDisableReason]
disabledIntervals := f.disabledIntervals(rules, mustSpecifyDisableReason, failures)
for _, currentRule := range rules {
ruleConfig := rulesConfig[currentRule.Name()]
currentFailures := currentRule.Apply(f, ruleConfig.Arguments)
@ -126,9 +129,15 @@ type enableDisableConfig struct {
position int
}
func (f *File) disabledIntervals(rules []Rule) disabledIntervalsMap {
re := regexp.MustCompile(`^//[\s]*revive:(enable|disable)(?:-(line|next-line))?(?::([^\s]+))?[\s]*$`)
const directiveRE = `^//[\s]*revive:(enable|disable)(?:-(line|next-line))?(?::([^\s]+))?[\s]*(?: (.+))?$`
const directivePos = 1
const modifierPos = 2
const rulesPos = 3
const reasonPos = 4
var re = regexp.MustCompile(directiveRE)
func (f *File) disabledIntervals(rules []Rule, mustSpecifyDisableReason bool, failures chan Failure) disabledIntervalsMap {
enabledDisabledRulesMap := make(map[string][]enableDisableConfig)
getEnabledDisabledIntervals := func() disabledIntervalsMap {
@ -202,16 +211,26 @@ func (f *File) disabledIntervals(rules []Rule) disabledIntervalsMap {
}
ruleNames := []string{}
if len(match) > 2 {
tempNames := strings.Split(match[3], ",")
for _, name := range tempNames {
name = strings.Trim(name, "\n")
if len(name) > 0 {
ruleNames = append(ruleNames, name)
}
tempNames := strings.Split(match[rulesPos], ",")
for _, name := range tempNames {
name = strings.Trim(name, "\n")
if len(name) > 0 {
ruleNames = append(ruleNames, name)
}
}
mustCheckDisablingReason := mustSpecifyDisableReason && match[directivePos] == "disable"
if mustCheckDisablingReason && strings.Trim(match[reasonPos], " ") == "" {
failures <- Failure{
Confidence: 1,
RuleName: directiveSpecifyDisableReason,
Failure: "reason of lint disabling not found",
Position: ToFailurePosition(c.Pos(), c.End(), f),
Node: c,
}
continue // skip this linter disabling directive
}
// TODO: optimize
if len(ruleNames) == 0 {
for _, rule := range rules {
@ -219,7 +238,7 @@ func (f *File) disabledIntervals(rules []Rule) disabledIntervalsMap {
}
}
handleRules(filename, match[2], match[1] == "enable", line, ruleNames)
handleRules(filename, match[modifierPos], match[directivePos] == "enable", line, ruleNames)
}
}

View File

@ -9,6 +9,6 @@ type FormatterMetadata struct {
// Formatter defines an interface for failure formatters
type Formatter interface {
Format(<-chan Failure, RulesConfig) (string, error)
Format(<-chan Failure, Config) (string, error)
Name() string
}

View File

@ -44,7 +44,7 @@ func main() {
var output string
go (func() {
output, err = formatter.Format(formatChan, config.Rules)
output, err = formatter.Format(formatChan, *config)
if err != nil {
fail(err.Error())
}
@ -62,6 +62,10 @@ func main() {
if c, ok := config.Rules[f.RuleName]; ok && c.Severity == lint.SeverityError {
exitCode = config.ErrorCode
}
if c, ok := config.Directives[f.RuleName]; ok && c.Severity == lint.SeverityError {
exitCode = config.ErrorCode
}
formatChan <- f
}